属性的特性
除了名字和值之外,属性还包含一些标识它们可写、可枚举、可配置的特性。在 ECMAScript 3 中无法设置这些特性,所有通过 ECMAScript 3 程序创建的属性都是可写、可枚举、可配置的,且无法对这些特性做修改。ECMAScript 5 则提供了查询和设置这些属性特性的 API,这些 API 对库开发者来说比较重要。
数据属性包含四个特性:值、可读性、可枚举性、可配置性。
存取器属性不具有值特性(由getter方法存在与否决定)和可写性(由setter方法存在与否决定),其四个特性是:读取、写入、可枚举性和可配置性。
为了实现属性特性的查询与设置操作,ECMAScript 5 定义了一个名为「属性描述符」的对象,这个对象代表了那四个特性。属性描述符对象的属性和所描述的属性特性是同名的,因此其属性有:value
、writable
、enumerable
、configurable
。存取器属性的描述符对象则用 get
和 set
来代替 value
和 writable
。
通过调用 Object.getOwnPropertyDescriptor()
可以获得某个对象特定属性值的属性描述符:
Object.getOwnPropertyDescriptor()
只能获取到自有属性的描述符,要想获取到继承属性的描述符,需要遍历原型链(通过 Object.getPrototypeOf()
方法):
要想设置属性的特性或者想要新建属性具有某种特性,需要调用 Object.defineProperty()
方法,传入要修改的对象、要创建或修改的属性名以及属性描述符对象:
注:新建属性不必包含所有四个属性,默认的特性值是
false
或undefined
。此外,Object.defineProperty()
方法也只能修改或新建自有属性,不能修改继承属性。
如果要同时修改或创建多个属性,可以使用 Object.defineProperties()
方法:
对于那些不允许修创建或修改的属性来说,调用 Object.defineProperty()
或 Object.defineProperties()
对其操作就会抛出类型错误异常。以下是完整的规则:
- 如果对象是不可扩展的,则可以编辑已有的自有属性,不能添加新属性
- 如果属性是不可配置的,则不能修改它的可配置性和可枚举性
- 如果存取器属性是不可配置的,则不能修改
getter
和setter
方法,也不能将其转换为数据属性 - 如果数据属性是不可配置的,则不能将其转换为存取器属性
- 如果数据属性是不可配置的,则不能将它的可写性从
false
修改为true
,但可以从true
修改为false
- 如果数据属性是不可配置且不可写的,则不能修改它的值,然而可配置不可写属性的值是可以修改的
属性特性应用示例 —— 编写extend
方法实现对象复制:
/**
* 给Object.prototype添加一个不可枚举的extend()方法
* 这个方法继承自调用它的对象,将作为参数传入的对象的属性一一复制
* 除了属性值之外,还会复制属性的所有特性,除非目标对象存在同名属性
*/
Object.defineProperty(Object.prototype, "extend", {
writable: true,
enumerable: false, // 将其定义为不可枚举
configurable: true,
value: function(o) {
var names = Object.getOwnPropertyNames(o);
// 遍历传入对象的所有自有属性
for (var i = 0; i < names.length; i++) {
// 如果属性已存在,跳过
if (names[i] in this) continue;
// 获取传入对象属性的描述符
var desc = Object.getOwnPropertyDescriptor(o, names[i]);
// 用它给this对象创建一个新属性
Object.defineProperty(this, names[i], desc);
}
}
});
使用示例:
No Comments