原文:http://www.2ality.com/2012/10/javascript-properties.html
在JavaScript中,属性决定了一个对象的状态,本文详细的研究了它们是如何工作的.
JavaScript中有三种不同类型的属性:命名数据属性(named data properties),命名访问器属性(named accessor properties)以及内部属性(internal properties).
这种属性就是我们通常所用的"普通"属性,它用来将一个字符串名称映射到某个值上.比如,下面的对象obj有一个名为字符串"prop"的数据属性,该属性的值为数字123.
var obj = {
prop: 123 };
你可以获取(读取)到一个属性的值:
console.log(obj.prop); // 123 console.log(obj["prop"]); // 123
你还可以设置(写入)一个属性的值:
obj.prop = "abc";
obj["prop"] = "abc";
另外,还可以借助函数来获取或设置一个属性的值.这些函数称之为访问器函数(accessor function).控制属性读取的访问器函数称之为getter.控制属性写入的访问器函数称之为setter.
var obj = {
get prop() {
return "Getter";
},
set prop(value) {
console.log("Setter: "+value);
}
}
让我们操作一下obj的属性:
> obj.prop
'Getter'
> obj.prop = 123;
Setter: 123
有一些属性仅仅是为规范所用的,称之为内部属性,因为它们无法通过JavaScript直接访问到,但是它们的确存在,并且影响着程序的表现.内部属性的名称比较特殊,它们都被两个中括号包围着.下面有两个例子:
一个属性的所有状态,包括它的数据和元数据,都存储在该属性的特性(attributes)中.属性拥有自己的特性,就像对象拥有自己的属性一样.特性的名称经常写成类似内部属性的形式(双中括号).
下面是命名数据属性拥有的特性:
下面是命名访问器属性拥有的特性:
下面是两种类型的属性都有的特性:
如果你不明确的指定某个特性的值,则它们会被赋一个默认值:
特性名称 | 默认值 |
[[Value]] | undefined |
[[Get]] | undefined |
[[Set]] | undefined |
[[Writable]] | false |
[[Enumerable]] | false |
[[Configurable]] | false |
这些默认值对于属性描述符尤其重要.
属性描述符(property descriptor)可以将一个属性的所有特性编码成一个对象并返回.该对象的每个属性都对应着所属属性的一个特性.例如,下面是一个值为123的只读属性的属性描述符:
{
value: 123,
writable: false,
enumerable: true,
configurable: false }
你也可以使用一个访问器属性来实现上面这个拥有只读特性的数据属性,其属性描述符如下:
{ get: function () { return 123 }, //没有set,也就是只读 enumerable: true, configurable: false }
在使用下面的函数时会用到属性描述符:
var obj = Object.defineProperty({}, "foo", {
value: 123,
enumerable: true // writable和configurable为默认值 });
var obj = Object.definePropertys({}, {
foo: { value: 123, enumerable: true },
bar: { value: "abc", enumerable: true }
});
var obj = Object.create(Object.prototype, {
foo: { value: 123, enumerable: true },
bar: { value: "abc", enumerable: true }
});
> Object.getOwnPropertyDescriptor(Object.prototype, "toString")
{ value: [Function: toString],
writable: true,
enumerable: false,
configurable: true }
> Object.getOwnPropertyDescriptor({}, "toString")
undefined
本节会解释什么操作会受到属性的可枚举性的影响,什么操作不会.我们首先假设已经定义了如下这样的对象proto和obj:
var proto = Object.defineProperties({}, {
foo: { value: 1, enumerable: true },
bar: { value: 2, enumerable: false }
});
var obj = Object.create(proto, {
baz: { value: 1, enumerable: true },
qux: { value: 2, enumerable: false }
});
需要注意的是,所有对象(包括上面的proto)通常来说都至少有一个原型Object.prototype [2]:
> Object.getPrototypeOf({}) === Object.prototype
true
我们常用的内置方法比如toString和hasOwnPropertyare等实际上都是定义在Object.prototype身上的.
可枚举性只影响两种操作:for-in循环和Object.keys().
for-in循环会遍历到一个对象的所有可枚举属性的名称,包括继承来的属性:
> for (var x in obj) console.log(x); //没有遍历到Object.prototype上不可枚举的属性qux baz foo
Object.keys()返回一个对象的所有可枚举的自身属性(非继承的)的名称组成的数组:
> Object.keys(obj)
[ 'baz' ]
如果你想获取到所有的自身属性,则应该使用Object.getOwnPropertyNames().
除了上面的两个操作,其他的操作都会忽略掉属性的可枚举性.一些读取操作会使用到继承来的属性:
> "toString" in obj
true > obj.toString
[Function: toString]
还有一些操作只会考虑自身属性:
> Object.getOwnPropertyNames(obj)
[ 'baz', 'qux' ]
> obj.hasOwnProperty("qux")
true > obj.hasOwnProperty("toString")
false > Object.getOwnPropertyDescriptor(obj, "qux")
{ value: 2,
writable: false,
enumerable: false,
configurable: false }
> Object.getOwnPropertyDescriptor(obj, "toString")
undefined
创建,删除,定义属性的操作只会影响到自身属性:
obj.propName = value
obj["propName"] = value
delete obj.propName
delete obj["propName"]
Object.defineProperty(obj, propName, desc)
Object.defineProperties(obj, descObj)
一般的规则是:系统创建的属性是不可枚举的,用户创建的属性是可枚举的:
> Object.keys([])
[]
> Object.getOwnPropertyNames([])
[ 'length' ]
> Object.keys(['a'])
[ '0' ]
特别是针对原型对象上的方法来说:
> Object.keys(Object.prototype)
[]
> Object.getOwnPropertyNames(Object.prototype)
[ hasOwnProperty',
'valueOf',
'constructor',
'toLocaleString',
'isPrototypeOf',
'propertyIsEnumerable',
'toString' ]
因此,在你自己写的代码中,通常不应该给内置的原型对象添加属性,如果你必须要这么做,则应该把这个属性设置为不可枚举的,以防止影响到其他代码.
正如我们所看到的,不可枚举的好处是:能确保已有的代码中的for-in语句不受到从原型继承来的属性的影响.但是,不可枚举的属性只能够创建一种"for-in只会遍历一个对象的自身属性"这样的幻觉.在你的代码中,仍应该尽可能避免使用for-in[3].
如果你把对象当成是字符串到值的Map来使用的话,则你应该只操作自身属性且要忽略掉可枚举性.不过这种情况下还有很多其他陷阱需要考虑[4].
在本文中,我们对属性的性质(称之为特性)进行了研究.需要注意的是,实际上JavaScript引擎并不是必须得通过特性来组织一个属性,它们主要是作为ECMAScript规范中定义的一个抽象操作.但有时候这些特性也会明确的出现在语言代码中,比如在属性描述符中.
更进一步的知识(2ality):