JavaScript属性设置与屏蔽

背景

翻阅《你所不知道的JavaScript(上卷)》(以下简称《不知道》)5.1.2节,关于属性设置和屏蔽时,看到设置myObject.foo = ‘bar’会出现三种情况。对于这三种情况,我的第一反应是,这三种情况原型链上的属性不都会被自身属性所屏蔽掉吗,有什么好讲的。结果继续往下读,

大多数开发者认为如果像[[Prototype]]链上层已经存在的属性[[put]]赋值,就一定会触发屏蔽,但是如你所见,三种情况中只有一种(第一种)是这样的。

好吧,我承认我就是那大多数开发者中的一员。

哪三种情况

  1. 如果在原型链的上层存在名为foo的数据属性,并且数据描述符writable为true,那就会直接在myObject中添加一个名为foo的新属性,从而屏蔽原型链上的属性。
    1
    2
    3
    4
    5
    6
    // 演示之用,直接扩展Object.prototype并不是明智之举
    Object.defineProperty(Object.prototype, 'foo', { writable: true, value: '你好' });
    var obj = {};
    obj.foo = '我不好';
    console.log(obj.hasOwnProperty('foo')); // true
    console.log(obj.foo); // 我不好
  2. 如果在原型链的上层存在名为foo的数据属性,而writable为false,那么无法修改已有的原型链上的foo,或者在myObject上创建屏蔽属性。如果在严格模式下,代码会报错,否则,赋值语句被忽略。不会发生屏蔽。
    1
    2
    3
    4
    5
    Object.defineProperty(Object.prototype, 'foo', { writable: false, value: '你好' });
    var obj = {};
    obj.foo = '我不好';
    console.log(obj.hasOwnProperty('foo')); // false
    console.log(obj.foo); // 你好
  3. 如果在原型链上层存在foo并且它是一个访问器属性,如果存在setter,那就一定会调用这个setter(ps:如果不存在,就直接结束)。foo不会被添加到(或者说屏蔽于)myObject,也不会重新定义foo这个setter。
    1
    2
    3
    4
    5
    6
    7
    8
    Object.defineProperty(Object.prototype, 'foo', {
    get: function () { return this._a_; }, set: function () { this._a_ = '你好我好大家好'; }
    });
    var obj = {};
    obj.foo = '我不好';
    console.log(obj.hasOwnProperty('foo')); // false
    console.log(obj.foo); // 你好我好大家好
    console.log(obj.hasOwnProperty('_a_')); //true

解释

第一种情况

我唯一对的一种情况╮(╯▽╰)╭,《不知道》中作者没解释,但很明显涉及到js内部[[set]] (《不知道》的作者称为[[put]])的过程,所以我查阅了ECMA规范中[[set]]部分。
英语不好,不翻译献丑了。
ecma set.jpg

第二种情况

《不知道》中作者给出的解释如下:

只读属性会阻止[[prototype]]链下层隐式创建(屏蔽)同名属性。这样做主要是为了模拟类的继承。你可以把原型链上层的foo看做是父类中的属性,它会被myObject继承(复制),这样一来myObject中的foo属性也是只读,所以无法创建。但是一定要注意,实际上并不会发生类似的继承复制。这看起来有点奇怪,myObject对象竟然会因为其他对象中有一个只读foo就不包含foo属性。

好吧,又是为了模拟类。

第三种情况

见第一种情况

技巧

如果希望在第二种和第三种情况下也屏蔽foo,那就不要使用=操作符,使用defineProperty()来向myObject添加foo属性。

1
2
3
4
5
Object.defineProperty(Object.prototype, 'foo', { writable: false, value: '你好' });
var obj = {};
Object.defineProperty(obj, 'foo', { value: '我不好' });
console.log(obj.hasOwnProperty('foo')); // true
console.log(obj.foo); // 我不好
1
2
3
4
5
6
7
8
Object.defineProperty(Object.prototype, 'foo', {
get: function () { return this._a_; }, set: function () { this._a_ = '你好我好大家好'; }
});
var obj = {};
Object.defineProperty(obj, 'foo', {value:'我不好'});
console.log(obj.hasOwnProperty('foo')); // true
console.log(obj.foo); // 我不好
console.log(obj.hasOwnProperty('_a_')); // false

参考资料