本篇文章需要对 JavaScript 的原型链 和属性描述符 有一定的了解。
本篇文章主要分析一下,在 JavaScript 给对象设置属性或是修改对象的属性值背后的过程。
首先看这样一段代码,熟悉一下原型链:
1 2 3 4 5 6 7 8 var obj = {}obj.a = 1 console .log (obj.a ) var obj2 = {}console .log (obj2.__proto__ === Object .prototype ) Object .prototype .a = 233 console .log (obj2.a )
我们分析一下:
obj1 没什么好说的,给当前对象上设置一个属性 a,然后读取该值。
obj2 虽然没有属性 a,但是 js 会在 obj2 的原型链上查找 a 属性,然后输出 233。
属性存在于原型链上而不存在于要赋值对象的三种情况
第一种情况:
如果在**[[prototype]]**链上存在同名属性,并且没有被标记为只读 writable:false ,创建屏蔽属性。
我们思考一下,如果有一个 obj3 原型链上有一个属性 foo ,然后仍然我们给 obj3 设置一个同名属性 foo,会发生什么? 实践一下:
1 2 3 4 5 var obj3 = {}Object .prototype .foo = 'i am prototype foo' obj3.foo = 'i am obj3 foo' console .log (obj3.foo ) console .log (Object .prototype .foo )
这样的输出是符合预期的,我们给 obj3 添加了一个 foo 属性,而不是修改 Object.prototype.foo 的值。
这种行为称之为属性屏蔽 ,obj3 的 foo 属性屏蔽了原型链上层的 foo 属性。
第二种情况:
如果在**[[prototype]]**链上存在同名属性,并且被被标记为只读 writable:false 。
非严格模式下:无法创建屏蔽属性,也无法修改链上的属性。
严格模式下,报错。
非严格模式:
1 2 3 4 5 6 7 8 var obj3 = {}Object .defineProperty (Object .prototype , 'foo' , { writable : false , value : 333 }) obj3.foo = 'i am obj3 foo' console .log (obj3.foo ) console .log (Object .prototype .foo )
严格模式:
1 2 3 4 5 6 7 8 9 'use strict' var obj3 = {}Object .defineProperty (Object .prototype , 'foo' , { writable : false , value : 333 }) obj3.foo = 'i am obj3 foo' console .log (obj3.foo )console .log (Object .prototype .foo )
第三种情况:
如果在**[[prototype]]链上存在同名属性,并且它是一个 [[setter]]**,那么就会调用它。
1 2 3 4 5 6 7 8 9 var obj3 = {}Object .defineProperty (Object .prototype , 'foo' , { set : function ( ) { console .log ('i am a setter!' ) } }) obj3.foo = 'i am obj3 foo' console .log (obj3.foo ) console .log (Object .prototype .foo )
可以到执行 obj3.foo 时,输出了 i am a setter! ,并且也没有发生属性屏蔽 。
可以看到,js 中设置一个属性并没有那个简单,除了第一种情况,剩下两种情况的行为都比较 “奇怪”,需要注意。
那么我们如何让 属性屏蔽 总是发生?
使用Object.defineProperty来屏蔽属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 var obj = {}Object .defineProperty (Object .prototype , 'foo1' , { writable : false , value : 'Object.prototype.foo1' }) Object .defineProperty (obj, 'foo1' , { value : 'obj.foo1' }) console .log (obj.foo1 ) console .log (Object .prototype .foo1 ) var obj2 = {}Object .defineProperty (Object .prototype , 'foo2' , { set : function ( ) { console .log ('i am foo2 setter' ) }, get : function ( ) { return 'get foo2 success!' } }) Object .defineProperty (obj2, 'foo2' , { value : 'obj.foo2' }) console .log (obj2.foo2 ) console .log (Object .prototype .foo2 )
可以看到,第二种和第三种情况都成功屏蔽了原型链上的同名属性!
参考链接: