今天看《你不知道的JavaScript》第五章——原型的时候,注意到一个关于JavaScript属性设置的有意思的地方。(P145)
之前,我以为除了对象被设置为不可扩展的情况,其他情况下给对象添加新属性都会成功。但没想到,还有其他不能添加新属性的情况。我所说的这种情况,就是原型链上有与你将要添加的属性同名的属性的时候。
分类
原型链上有与你将要添加的属性同名的属性的情况,还要分成三种情况:
原型链上有同名的数据属性并且没有被标记为只读,即
writable: true
。原型链上有同名的数据属性,但它被标记为只读,即
writable: false
。原型链上有同名的存取器属性,且至少设置了
setter
。
规则
这里以设置myObject.foo = 'my'
为例。
同名数据属性、可读
如果在[[Prototype]]链上层存在名为foo的普通数据访问属性并且没有被标记为只读(wirtable: false),那么就会在myObject中添加一个名为foo的新属性,它就是屏蔽属性。
这种情况是最常见的,下面贴一个简单的例子。
var proObject = {
foo: 'pro'
}
var myObject = Object.create(proObject)
myObject.foo = 'my'
myObject.foo // 'my'
myObject.hasOwnProperty('foo') // true
同名数据属性、只读
如果在[[Prototype]]链上层存在foo,但是它被标记为只读(writable: false),那么无法修改已有属性或者在myObject上创建屏蔽属性。如果运行在严格模式下,代码会抛出一个错误。否则,这条赋值语句会被忽略。
var proObject = {}
Object.defineProperty(proObject, 'foo', {
value: 'pro',
wirtable: false
})
var myObject = Object.create(proObject)
myObject.foo = 'my'
myObject.foo // 'pro'
myObject.hasOwnProperty('foo') // false
'use strict'
myObject.foo = 'my' // Uncaught TypeError: Cannot assign to read only property 'foo' of object '#<Object>'
同名的存取器属性
如果在[[Prototype]]链上层存在foo并且它是一个setter,那就一定会调用这个setter。foo不会被添加到(或者说屏蔽于)myObject,也不会重新定义foo这个setter。
var proObject = {}
Object.defineProperty(proObject, 'foo', {
set: function(val) {
this.s = val
},
get: function() {
return this.s
}
})
myObject.foo = 'my'
myObject.hasOwnProperty('foo') // false
// 可以看到存取器属性没有被重新定义
Object.getOwnPropertyDescriptor(proObject, 'foo')
解决方案
如果你希望在上述的第二和第三中情况下为myObject添加新属性的话,你需要使用Object.defineProperty
或者Object.getOwnPropertyDescriptors
来添加新属性。
结语
终于在周日完成了这周的博客文章了(虽然很无耻地“水了一篇”,但好歹也算一篇文章嘛。)
正经一点!!!JavaScript中还有很多让我们出乎意料的地方,虽然平时很少遇到这些方面知识的应用,但一旦踩了这些坑,还是会耗掉我们挺多时间和精力的。所以,我们平时应该多留意这些知识,并积累下来。那么当我们遇到关于这些知识的bug的时候,就会很快将问题解决了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。