一、引入

从 ES5 开始,所有的属性都具备了属性描述符。

  1. var myObject = {
  2.   a: 2
  3. }
  4. Object.getOwnPropertyDescriptor(myObject, 'a')
  5. // {value: 2, writable: true, enumerable: true, configurable: true}

这个普通的对象属性对应的属性描述符可不仅仅只是一个 2。它还包含另外三个特性,writable(不可写)、enumerable(可枚举)、configurable(可配置)。在创建普通属性时属性描述符会使用默认值。我们可以使用 Object.defineProperty(…) 来添加一个新属性或者修改一个已有属性

  1. var myObject = {};
  2. Object.defineProperty(myObject, 'a', {
  3.   value: 2,
  4.   writable: true,
  5.   configurable: true,
  6.   enumerable: true
  7. })

我们使用 defineProperty(…)添加了一个普通的属性,然而,一般来说不会使用这种方式添加,除非你想修改属性描述符

二、各个属性描述符的解释和使用

2.1 writable

writable决定是否可以修改属性的值

  1. var myObject = {};
  2. Object.defineProperty(myObject, 'a', {
  3.   value: 2,
  4.   writable: false, // 不可写
  5.   configurable: true,
  6.   enumerable: true
  7. })
  8. myObject.a = 3;
  9. myObject.a; // 2

我们对于属性值的修改失败了,如果在严格模式下,会出错,TypeError

2.2 configurable

只要属性是可配置的,就可以使用 defineProperty(…) 方法来修改属性描述符。

  1. var myObject = {
  2.   a: 2
  3. }
  4. myObject.a = 3;
  5. myObject.a; // 3
  6. Object.defineProperty(myObject, 'a', {
  7.   value: 4,
  8.   writable: true,
  9.   configurable: false, // 不可配置
  10.   enumerable: true
  11. })
  12. myObject.a; // 4
  13. myObject.a = 5; //可以修改值
  14. myObject.a; // 5
  15. Object.defineProperty(myObject, 'a', {
  16.   value: 6
  17. })
  18. myObject.a; // 6
  19. Object.defineProperty(myObject, 'a', {
  20.   writable: false
  21. })// 不报错
  22. myObject.a = 7;
  23. myObject.a // 6
  24. Object.defineProperty(myObject, 'a', {
  25.   writable: true
  26. })
  27. // Uncaught TypeError: Cannot redefine property: a
  28. // at Function.defineProperty (<anonymous>)
  29. // at <anonymous>:2:8
  30. Object.defineProperty(myObject, 'a', {
  31.   configurable: true
  32. }) //TypeError
  33. Object.defineProperty(myObject, 'a', {
  34.   enumerable: false
  35. }) //TypeError

不管是不是处于严格模式,修改一个不可配置的属性描述符都会出错,把 configurable 修改成 false 是单向操作,无法撤销.
即便属性是 configurable:false,还是可以把 writable 改成 false,但是无法由 false 改成 true,这是个例外。
configuable:fase 还会禁止删除这个属性

var myObject = {};
Object.defineProperty(myObject, 'a', {
  value: 4,
  writable: true,
  configurable: false, // 不可配置
  enumerable: true
})
delete myObject.a;
myObject.a; // 2

2.3 enumerable

这个描述符控制的是属性是否出现在对象的属性枚举中,比如 for…in 循环,如果把 enumerable 设置成 false,这个属性就不会出现在枚举中。

2.4 get 和 set 函数

除了使用点语法或中括号语法访问属性的 value 外,还可以使用访问器,包括 set 和 get 两个函数。其中,set() 函数可以设置 value 属性值,而 get() 函数可以读取 value 属性值。
借助访问器,可以为属性的 value 设计高级功能,如禁用部分特性、设计访问条件、利用内部变量或属性进行数据处理等。

2.4.1 示例1

下面示例设计对象 obj 的 x 属性值必须为数字。为属性 x 定义了 get 和 set 特性,obj.x 取值时,就会调用 get;赋值时,就会调用 set。

var obj = Object.create(Object.prototype, {
    _x : {  //数据属性
        value : 1,  //初始值
        writable : true
    },
    x : {  //访问器属性
        get : function () {  //getter
            return this._x;  //返回_x属性值
        },
        set : function (value) {  //setter
            if (typeof value != "number") throw new Error('请输入数字');
            this._x = value;  //赋值
        }
    }
});
console.log(obj.x);  //1
obj.x = "2";  //抛出异常

2.4.2 示例2

JavaScript 也支持一种简写方法。针对示例 1,通过以下方式可以快速定义属性。

var obj = {
    _x : 1,  //定义_x属性
    get x() { return this._x },  //定义x属性的getter
    set x(value) {  //定义x属性的setter
        if (typeof value != "number") throw new Error('请输入数字');
        this._x = value;  //赋值
    }
};
console.log(obj.x);  //1
obj.x = 2;
console.log(obj.x);  //2

注意:JavaScript的set 和get中不能访问自己,会导致递归死循环

var obj = {
    x : 1,  //定义_x属性
    get x() { return this.x },  //定义x属性的getter
    set x(value) {  //定义x属性的setter
        if (typeof value != "number") throw new Error('请输入数字');
        this.x = value;  //赋值
    }
};
obj.x //Uncaught RangeError: Maximum call stack size exceeded