进入正文之前,我们先来做两道题:
题1:
let obj = {
name: "ikun",
};
let obj1 = {
fun: "xiaoheizi",
};
Object.setPrototypeOf(obj1, obj);
obj1.name = "cai";
console.log(obj1);
/*输出结果为:
{fun: 'xiaoheizi', name: 'cai'}
*/
题2:
let obj = {
// name: "ikun",
};
Object.defineProperty(obj, "name", {
writable: false,
value: "ikun",
});
let obj1 = {
fun: "xiaoheizi",
};
Object.setPrototypeOf(obj1, obj);
obj1.name = "cai";
console.log(obj1);
//{fun: 'xiaoheizi'}
why?为什么会出现这种情况呢?让我们继续往下研究:
属性设置和屏蔽
给一个对象设置属性并不仅仅是加一个新属性或者修改已有的属性值。现在我们完整地讲解一下这个过程:
myObject.foo =" bar ";
- 如果my0bject对象中包含名为 foo 的普通数据访问属性,这条赋值语句只会修改已有的属性值。
- 如果 foo 不是直接存在于my0bject中,[[ Prototype ]]链就会被遍历,类似[[ Get ]]操作。
- 如果原型链上找不到 foo , foo 就会被直接加到my0bject上。
然而,如果 foo 存在于原型链上层,赋值语句 myObject . foo =" bar "的行为就会有些不同
(而且可能很出人意料)。稍后我们会进行介绍。
如果属性名 foo 既出现在my0bject中也出现在my0bject的[[ Prototype ]]链上层,那么就会发生屏蔽。my0bject中包含的 foo 属性会屏蔽原型链上层的所有 foo 属性,因为myObject . foo 总是会选择原型链中最底层的 foo 属性。
屏蔽比我们想象中更加复杂。下面我们分析一下如果 foo 不直接存在于 myObject 中而是存在于原型链上层时 myObject.foo="bar",会出现的三种情况:
- 如果在[[ Prototype ]]链上层存在名为 foo 的普通数据访问属性,并且没有被标记为只读( writable : false ),那就会直接在myObject中加一个名为 foo 的新属性,它是屏蔽属性
- 如果在[[ Prototype ]]链上层存在 foo ,但是它被标记为只读( writable : false ),那么无法修改已有属性或者在my0bject上创建屏蔽属性。如果运行在严格模式下,代码会抛出一个错误。否则,这条赋值语句会被忽略。总之,不会发生屏蔽。
- 如果在[[ Prototype ]]链上层存在 foo 并且它是一个 seter ,那就一定会调用这个 seter 。 foo 不会被添加到(或者说屏蔽于) myObject ,也不会重新定义 foo 这个 setter 。
大多数开发者都认为如果向[[ Prototype ]]链上层已经存在的属性([[ Put ]])赋值,就一定会触发屏蔽,但是如你所见,三种情况中只有一种(第一种)是这样的。
如果你希望在笔二种和第三种情况下也屏蔽 foo 那键不能使甩=操作符来赋值,而是使用Object.defineProperty(...) 来向myObject添加 foo
第二种情况可能是最令人意外的,只读属性会阻止[[ Prototype ]]链下层隐式创建(屏蔽)同名属性。这样做主要是为了模拟类属性的继承,你可以把原型链上层的 foo 看作是父类中的属性、它会被 myObject 继承(复制),这样一来 myObject 中的 foo 属性也是只读,所以无法创建。但是一定要注意,实际上并不会发生类似的继承复制。
这看起来有点奇怪, myObject 对象竟然会因为其他对象中有一个只读 foo 就不能包含 foo 属性。更奇怪的是,这个限制只存在于 =赋值中,使用 Object .defineProperty并不会受到影响。我们开发的时候一定要注意。