属性的特性


除了名字和值之外,属性还包含一些标识它们可写、可枚举、可配置的特性。在 ECMAScript 3 中无法设置这些特性,所有通过 ECMAScript 3 程序创建的属性都是可写、可枚举、可配置的,且无法对这些特性做修改。ECMAScript 5 则提供了查询和设置这些属性特性的 API,这些 API 对库开发者来说比较重要。

数据属性包含四个特性:值、可读性、可枚举性、可配置性。

存取器属性不具有值特性(由getter方法存在与否决定)和可写性(由setter方法存在与否决定),其四个特性是:读取、写入、可枚举性和可配置性。

为了实现属性特性的查询与设置操作,ECMAScript 5 定义了一个名为「属性描述符」的对象,这个对象代表了那四个特性。属性描述符对象的属性和所描述的属性特性是同名的,因此其属性有:valuewritableenumerableconfigurable。存取器属性的描述符对象则用 getset 来代替 valuewritable

通过调用 Object.getOwnPropertyDescriptor() 可以获得某个对象特定属性值的属性描述符:

获取属性描述符

获取属性描述符

Object.getOwnPropertyDescriptor() 只能获取到自有属性的描述符,要想获取到继承属性的描述符,需要遍历原型链(通过 Object.getPrototypeOf() 方法):

遍历原型链获取继承属性描述符

要想设置属性的特性或者想要新建属性具有某种特性,需要调用 Object.defineProperty() 方法,传入要修改的对象、要创建或修改的属性名以及属性描述符对象:

设置属性特性

注:新建属性不必包含所有四个属性,默认的特性值是 falseundefined。此外,Object.defineProperty() 方法也只能修改或新建自有属性,不能修改继承属性。

如果要同时修改或创建多个属性,可以使用 Object.defineProperties() 方法:

同时修改或创建多个属性

对于那些不允许修创建或修改的属性来说,调用 Object.defineProperty()Object.defineProperties() 对其操作就会抛出类型错误异常。以下是完整的规则:

  • 如果对象是不可扩展的,则可以编辑已有的自有属性,不能添加新属性
  • 如果属性是不可配置的,则不能修改它的可配置性和可枚举性
  • 如果存取器属性是不可配置的,则不能修改 gettersetter 方法,也不能将其转换为数据属性
  • 如果数据属性是不可配置的,则不能将其转换为存取器属性
  • 如果数据属性是不可配置的,则不能将它的可写性从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);
        }
    }
});

使用示例:

对象复制


点赞 取消点赞 收藏 取消收藏

<< 上一篇: 属性 getter 和 setter

>> 下一篇: 对象的三个属性