前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >《你不知道的JavaScript》:原型链访问的坑

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

作者头像
前端_AWhile
发布2019-08-29 13:12:28
5520
发布2019-08-29 13:12:28
举报
文章被收录于专栏:前端一会前端一会

本篇开始看下js对象原型[[Prototype]]

js中的对象有一种特殊的内置属性 [[Prototype]],其实就是对于其他对象的引用。几乎所有的对象在创建时都 [[Prototype]]属性都会被赋予一个非空的值。

代码语言:javascript
复制
1var obj = {
2    a: 2
3}
4console.log(obj.a);     // 2
5var newObj = Object.create(obj);
6console.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属性设置值时:

代码语言:javascript
复制
1obj.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。
代码语言:javascript
复制
 1// 1.
 2var parentObj = {foo: 10};
 3var obj = Object.create(parentObj);
 4obj.foo = 5;
 5console.log(obj.foo);       // 5
 6
 7// 2.
 8var parentObj = {};
 9Object.defineProperty(parentObj, "foo", {
10    value: 10,
11    writable: false
12});
13var obj = Object.create(parentObj);
14obj.foo = 5;
15console.log(obj.foo);   // 10 无法修改已有属性或在obj对象上创建屏蔽属性 非严格模式时忽略obj.foo = 5;操作,严格模式时直接报错
16
17// 3.
18var parentObj = {
19    get foo(){
20        return this.res;
21    },
22    set foo(val){
23        this.res = val * 4;
24    }
25}
26parentObj.foo = 10;
27console.log(parentObj.foo);     // 40
28
29var obj = Object.create(parentObj);
30obj.foo = 3;
31console.log(obj.foo);    // 12  继续调用原型链上层对象上 setter,
32console.log(obj.hasOwnProperty("foo"));     // false  并且foo还不会被添加到obj对象上

如果希望在第2和第3种情况也能屏蔽foo属性,就不能使用=赋值操作符,而应使用Object.defineProperty()来向obj对象添加foo。

代码语言:javascript
复制
 1// 2.
 2var parentObj = {};
 3Object.defineProperty(parentObj, "foo", {
 4    value: 10,
 5    writable: false
 6});
 7var obj = Object.create(parentObj);
 8Object.defineProperty(obj, "foo", {
 9    value: 5,
10    writable: false
11})
12console.log(obj.foo);       // 5 这回可以在obj对象创建屏蔽属性foo了,值也为最新值 5
13
14// 3.
15var parentObj = {
16    get foo(){
17        return this.res;
18    },
19    set foo(val){
20        this.res = val * 4;
21    }
22}
23parentObj.foo = 10;
24console.log(parentObj.foo);     // 40
25
26var obj = Object.create(parentObj);
27Object.defineProperty(obj, "foo", {
28    value: 7
29})
30console.log(obj.foo);   // 7

你看,所以如果确实想改,也是有办法的,用Object.defineProperty()就行。

-------------------------------- 热门文章 --------------------------------

设计模式>>>

javascript设计模式一: 单例模式

javascript设计模式二:策略模式

javascript设计模式三:代理模式

javascript设计模式四:迭代器模式

javascript设计模式五:原型模式

javascript设计模式六:发布-订阅模式(观察者模式)

javascript设计模式七:模板方法模式

javascript设计模式八:职责链模式

javascript设计模式九:中介者模式

javascript设计模式十:装饰者模式

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-01-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端小二 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档