Object.defineProperty的兼容性问题和解决方案

1. 语法

Object.defineProperty(obj, prop, descriptor)

  • obj:要在其上定义属性的对象。
  • prop:要定义或修改的属性的名称。
  • descriptor:将被定义或修改的属性描述符。
  • 返回值:被传递给函数的对象。

2. 属性描述符

  • 创建属性

如果对象中不存在指定的属性,Object.defineProperty() 就创建这个属性。当描述符中省略某些字段时,这些字段将使用它们的默认值——拥有布尔值的字段的默认值都是false;value,get 和 set 字段的默认值为 undefined。一个没有get/set/value/writable定义的属性被称为“通用的”,并被“键入”为一个数据描述符。

  • 修改属性
1. Writable:当 writable 属性设置为 false 时,该属性被称为“不可写”。它不能被重新分配。试图写入非可写属性不会改变它,也不会引发错误。
2. Enumerable:enumerable 定义了对象的属性是否可以在 for...in 循环和 Object.keys() 中被枚举。
3. Configurable:configurable 特性表示对象的属性是否可以被删除,以及除 writable 特性外的其他特性是否可以被修改。
  • 添加多个属性和默认值

    通常,使用点运算符和 Object.defineProperty() 为对象的属性赋值时,数据描述符中的属性默认值是不同的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    var o = {};

    o.a = 1;
    // 等同于 :
    Object.defineProperty(o, "a", {
    value : 1,
    writable : true,
    configurable : true,
    enumerable : true
    });


    // 另一方面,
    Object.defineProperty(o, "a", { value : 1 });
    // 等同于 :
    Object.defineProperty(o, "a", {
    value : 1,
    writable : false,
    configurable : false,
    enumerable : false
    });
  • 一般的 Setter 和 Getter

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    function Archiver() {
    var temperature = null;
    var archive = [];

    Object.defineProperty(this, 'temperature', {
    get: function() {
    console.log('get!');
    return temperature;
    },
    set: function(value) {
    temperature = value;
    archive.push({ val: temperature });
    }
    });

    this.getArchive = function() { return archive; };
    }

    var arc = new Archiver();
    arc.temperature; // 'get!'
    arc.temperature = 11;
    arc.temperature = 13;
    arc.getArchive(); // [{ val: 11 }, { val: 13 }]

3. 重定义数组对象的 length 属性

数组的 length 属性是可以重定义的,但是会受到一般的重定义限制。(length 属性初始化为 non-configurable,non-enumerable,以及 writable。对于一个内容不可变的数组,改变其 length 属性的值或者使它变为 non-writable 是可以的。但是改变其可枚举性和可配置性或者当它是 non-writable 时尝试改变它的值或是可写性,这两者都是不被允许的。)然而,并不是所有浏览器都允许 Array.length 的重定义。只在 Internet Explorer 9 及以后版本和 Firefox 23 及以后版本中,才完整地正确地支持数组 length 属性的重新定义。

4. IE 8 具体案例

Internet Explorer 8 实现了 Object.defineProperty() 方法,但 只能在 DOM 对象上使用。 需要注意的一些事情:

  • 尝试在原生对象上使用 Object.defineProperty() 会报错。
  • 属性特性必须设置一些特定的值。对于数据属性描述符,configurable, enumerable 和 writable 特性必须全部设置为 true;对于访问器属性描述符,configurable 必须设置为 true,enumerable 必须设置为 false。试图提供其他值将导致一个错误抛出。
  • 重新配置一个属性首先需要删除该属性。如果属性没有删除,就如同重新配置前的尝试。