《你不知道的JavaScript》:原型链访问的坑

版权声明:本文为微信公众号前端小二版权所有,如需转载可私信或关注文末微信公众号留言。 https://blog.csdn.net/qq_34832846/article/details/86488101

本篇开始看下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.anewObj.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";会按照不同情况来执行:(下面有对应代码示例)
    1. 如果原型链上层对象存在名为foo的普通访问数据属性并且没有被标记为只读(writable: false),那就会直接在obj对象中添加一个名foo的新属性,它是屏蔽属性。
    2. 如果原型链上层对象存在foo属性并且还被标记为只读(writable: false),那么就无法修改已有属性或在obj对象上创建屏蔽属性。如果运行在严格模式下,代码还会抛出一个错误,否则这条语句会被忽略。
    3. 如果原型链上层对象存在foo属性并且它是一个setter,那就一定会调用这个setter,foo不会被添加到obj对象上,也不会重新定义这个setter。
// 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()就行。

喜欢本文请扫下方二维码,关注微信公众号: 前端小二,查看更多我写的文章哦,多谢支持。
在这里插入图片描述

扫描二维码关注公众号,回复: 5044980 查看本文章

猜你喜欢

转载自blog.csdn.net/qq_34832846/article/details/86488101