本篇文章主要讲解了JS中对象属性赋值时,会出现的种种情况,在开始之前,为了加深大家的印象,我们来假设一个场景。
让我们来先编个故事
这是一个凄惨/圆满的爱情故事,让我们慢慢来说。
很久以前,有一个很漂亮的女孩,她叫做小菊。
var 小菊 = {
face: 'beauty'
}
然后呢有一个小屁孩叫做小明
var 小明 = {}
小明很喜欢小菊,但是却一直没敢告诉她,他每天等她下课,接送她回家,他已经觉得很幸福了。 有一天,放学时分,小明还是照旧等在校门口,四处张望着,看到小菊走过来,原本倚在墙边的他立马站直了起来,手里似乎攥着什么。 小菊走过他身边,转过头对他说了一声“走吧”。 小明身体紧张得身体前倾了一下,支支吾吾的说道,“等...等一下”。 小菊停住了脚步,回头一个黑人问号。 “我...我不太会说话,给你看这个”,说完小明将手递过去,是一张纸条。 小菊接过纸条,一脸疑惑的打开。 上面写道:
var 小菊 = {}
var 小明 = {
heart: 小菊,
wait () {
console.log('I love you')
}
}
while(小菊.heart !== 小明) {
小明.wait()
}
交往(小菊, 小明)
function 交往(p1, p2) {
console.log(`${p1.name}和${p2.name}在一起啦`)
}
小明接着说道,“我的程序死掉了,你能帮一下我吗?”
小菊抬起头,一脸懵逼得看着小明,“我没学过编程哦,不好意思。”
只见小菊从背包里面翻出一只笔来,在纸上加了一行
// ...
while(小菊.heart !== 小明) {
小明.wait()
+ 小菊.heart = 小明
}
然后抬起头,对小明说:“现在我的心里面有你了,你的程序不会死循环了。” 小明激动地抱住了小菊。 ...
对象属性赋值
咳咳,我们回到正题啊。
让我们来看看这句小菊.heart = 小明,看似很正常,却暗藏杀机的语句。
如果小菊之前没谈过恋爱,那么结果正如我们预期。
但是,如果小菊之前有过感情经历
var 小菊 = Object.create({ heart: "小强" });
小菊.heart === '小强' // true
重点来了(敲黑板),
小菊.heart = 小明
小菊.heart === 小明 // true
看上去很美好哦,不过我们来看看
小菊.__proto__.heart === '小强' // true
???也就是说,当我们执行以下代码
Reflect.deleteProperty(小菊, "heart");
这个时候
小菊.heart === '小强' // true
OK,我们要怎么来理解这个现象呢?
首先我们知道,当在对象上找不到某个属性时,JS引擎会在在其原型链上继续查找,直到找到或者到达原型链末端为止。
所以,从这个过程中我们可以看出,小菊.heart它并不是对某个确定的属性的一个引用,而是查找的一个返回结果。
它会取到访问链最前端的那个,所以也会造成属性覆盖的现象。
但是为什么,小菊.heart = 小明没有将原型链上的同名属性覆盖掉呢?
Setting a property to an object creates an own property. The only exception to the getting and setting behavior rules is when there is an inherited property with a getter or a setter.
::: hljs-right -- MDN :::
这里说 如果没有继承来的setter或者getter,当给对象设置属性时,只会添加到它自己身上
所以JS引擎并没有选择重写掉原型链上的属性。
这也就带来了一些问题,比如:
var obj1 = {
a: 1
};
var obj2 = Object.create(obj1);
var obj3 = Object.create(obj1);
obj2.a++;
console.log(obj2.a); // 2
console.log(obj3.a); // 1
当我们想要共享原型上的属性时,却都自己创建了一个副本。
给属性加上setter/getter
如果访问者的属性是被继承的,它的 get 和set 方法会在子对象的属性被访问或者修改时被调用
-- MDN
我们增加一个getter来试试效果
var obj1 = {
get a() {
return 1;
}
};
var obj2 = Object.create(obj1);
obj2.a++;
console.log(obj2); // {}
这个时候,赋值操作没有用了,当设置了setter时也一样。
var obj1 = {
_a: 1,
set a(val) {
obj1._a = val;
},
get a() {
return obj1._a;
}
};
var obj2 = Object.create(obj1);
obj2.a++;
现在,对于原型链上属性的修改就能直接重写原来的值,而不是自己创建一个属性。
修改属性的property
当涉及到读写属性时,我们就会想到属性描述符
var obj1 = {};
Object.defineProperty(obj1, "a", {
writable: false,
enumerable: true,
configurable: true,
value: 1
});
var obj2 = Object.create(obj1);
obj2.a++;
console.log(obj2); // {}
当我们将对象原型链上属性的writable设置为false时,对象也不会自己创建一个属性。
那我非要给自己建个属性呢?
当有继承setter/getter,或writable设置为false的时候,如若想要创建自有属性。可以使用Object.defineProperty进行设置。
结语
所以这里的对象有三种情况:
- 对象的原型属性啥都没设置 => 直接遮盖,但不会影响原型属性
- 对象的原型属性上有设置setter/getter => 按照继承的setter/getter来重写/获取
- 对象的原型属性设置为不可写 => 不能对该属性进行重写
OK,这样我们就清楚了,到底谁是渣对象!所以各位,清楚原理很重要,别被表象冲昏了头脑。