本篇开始看下js对象原型[[Prototype]]
。
js中的对象有一种特殊的内置属性 [[Prototype]]
,其实就是对于其他对象的引用。几乎所有的对象在创建时都 [[Prototype]]
属性都会被赋予一个非空的值。
var obj = {
a: 2
}
console.log(obj.a); // 2
var newObj = Object.create(obj);
console.log(newObj.a); // 2
上例的newObj
对象的[[Prototype]]
属性指向obj
对象。
当试图引用对象的属性时会触发[[Get]]
操作,例如obj.a
和newObj.a
。对于默认的[[Get]]
操作来说,第一步是检查对象本身是否有这个属性,如果有的话就使用它。如果对象本身没有这个属性,就需要使用到对象的内置属性 [[Prototype]]
了,[[Get]]
操作会访问对象的[[Prototype]]
链,对于上例的newObj.a
操作而言就是会继续访问其原型链上层的obj
对象。
现在我们明白访问对象的属性时,会先查找对象本身,如本身没有对应属性时,会向该对象的原型链上层对象查找,找到则返回该属性的值,如始终没有找到,则返回undefined
。
那么这个始终没有找到的尽头在哪?就在Object.prototype
。它是js中所有对象的源头,Object.prototype
的再上一层也有,但是null
了。
不光访问对象的属性可能会查找其原型链,为对象属性设置值时同样也可能会查找该对象的原型链。
通常为对象属性设置值我们采用=
赋值操作符来进行,当为对象obj的foo属性设置值时:
obj.foo = "bar";
- 如果obj对象中包含名为foo的普通数据访问属性,这条赋值语句只会修改已有的属性值。
- 如果foo不是直接存在于obj对象上,该对象的原型链就会被遍历,如果原型链上层所有对象都没有foo属性时,foo就直接添加到obj对象上。
- 如果属性名foo既出现在obj对象上也出现在上层原型对象上,那就会发生屏蔽,obj对象中包含的foo属性会屏蔽原型链上层的所有foo属性,因为
obj.foo
总是会选择原型链中最底层的foo属性。 - 但如果foo不直接存在于obj对象而是存在于原型链上层对象,赋值语句
obj.foo = "bar";
会按照不同情况来执行:(下面有对应代码示例)- 如果原型链上层对象存在名为foo的普通访问数据属性并且没有被标记为只读(
writable: false
),那就会直接在obj对象中添加一个名foo的新属性,它是屏蔽属性。 - 如果原型链上层对象存在foo属性并且还被标记为只读(
writable: false
),那么就无法修改已有属性或在obj对象上创建屏蔽属性。如果运行在严格模式下,代码还会抛出一个错误,否则这条语句会被忽略。 - 如果原型链上层对象存在foo属性并且它是一个setter,那就一定会调用这个setter,foo不会被添加到obj对象上,也不会重新定义这个setter。
- 如果原型链上层对象存在名为foo的普通访问数据属性并且没有被标记为只读(
// 1.
var parentObj = {foo: 10};
var obj = Object.create(parentObj);
obj.foo = 5;
console.log(obj.foo); // 5
// 2.
var parentObj = {};
Object.defineProperty(parentObj, "foo", {
value: 10,
writable: false
});
var obj = Object.create(parentObj);
obj.foo = 5;
console.log(obj.foo); // 10 无法修改已有属性或在obj对象上创建屏蔽属性 非严格模式时忽略obj.foo = 5;操作,严格模式时直接报错
// 3.
var parentObj = {
get foo(){
return this.res;
},
set foo(val){
this.res = val * 4;
}
}
parentObj.foo = 10;
console.log(parentObj.foo); // 40
var obj = Object.create(parentObj);
obj.foo = 3;
console.log(obj.foo); // 12 继续调用原型链上层对象上 setter,
console.log(obj.hasOwnProperty("foo")); // false 并且foo还不会被添加到obj对象上
如果希望在第2和第3种情况也能屏蔽foo属性,就不能使用=
赋值操作符,而应使用Object.defineProperty()
来向obj对象添加foo。
// 2.
var parentObj = {};
Object.defineProperty(parentObj, "foo", {
value: 10,
writable: false
});
var obj = Object.create(parentObj);
Object.defineProperty(obj, "foo", {
value: 5,
writable: false
})
console.log(obj.foo); // 5 这回可以在obj对象创建屏蔽属性foo了,值也为最新值 5
// 3.
var parentObj = {
get foo(){
return this.res;
},
set foo(val){
this.res = val * 4;
}
}
parentObj.foo = 10;
console.log(parentObj.foo); // 40
var obj = Object.create(parentObj);
Object.defineProperty(obj, "foo", {
value: 7
})
console.log(obj.foo); // 7
你看,所以如果确实想改,也是有办法的,用Object.defineProperty()
就行。
喜欢本文请扫下方二维码,关注微信公众号: 前端小二,查看更多我写的文章哦,多谢支持。