Object.defineProperty

Updated at 2022.09.26, 1191 words, 5 mins
Table of Contents

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。该方法允许精确添加或修改对象的属性。通过赋值来添加的普通属性会创建在属性枚举期间显示的属性(for...inObject.keys 方法),这些值可以被改变,也可以被删除。这种方法允许这些额外的细节从默认值改变。默认情况下,使用 Object.defineProperty() 添加的属性值是不可变的。

语法

Object.defineProperty(obj, prop, descriptor)

参数

  • obj: 需定义属性的对象
  • prop: 要定义或修改的属性的名称
  • descriptor: 将被定义或修改的属性描述符

这里主要介绍研究一下第三个参数 descriptor

属性描述符

属性描述符分为两种:数据描述符存取描述符。描述符必须是这两种形式之一,不能同时是两者。

数据描述符存取描述符均具有以下可选属性:

  • configurable: 默认为 false
  • enumerable: 默认为 false

数据描述符单独具有的可选属性:

  • value: 属性对应的值
  • writable: 默认为 false

存取描述符单独具有的可选属性:

  • get: 一个给属性提供 getter 的方法,默认为 false
  • set: 一个给属性提供 setter 的方法,默认为 false

如果一个描述符不具有 value、writable、get 和 set 任意一个关键字,那么它将被认为是一个数据描述符。 如果一个描述符同时有(value 或 writable)和(get 或 set)关键字,将会产生一个异常(数据描述符和存取描述符不能同时存在)。

Configurable

configurable 特性指明对象的属性是否可以被删除,以及除 writable 特性外的其他特性是否可以被修改。

var obj = {};
Object.defineProperty(obj, "a", {
  configurable : false,
  get : function(){
    return 1;
  },
});

// 以下几条语句全部抛出错误,因为除了 writable 之外的其他属性都是不可修改的
Object.defineProperty(obj, "a", {configurable: true});
Object.defineProperty(obj, "a", {enumerable: true});
Object.defineProperty(obj, "a", {set : function(){}});
Object.defineProperty(obj, "a", {get : function(){return 1;}});
Object.defineProperty(obj, "a", {value : 12});

console.log(obj.a); // 1
delete obj.a; // Nothing happens
console.log(obj.a); // 1

如果 configurabletrue,那么在删除某个属性的时候,该属性下的 configurable 是最后被删除的。

Enumerable

enumerable 定义了对象的属性是否可以在 for...in 循环和 Object.keys() 中被枚举。

var obj = {};
Object.defineProperty(obj, "a", { value : 1, enumerable:true });
Object.defineProperty(obj, "b", { value : 2, enumerable:false });
Object.defineProperty(obj, "c", { value : 3 }); // enumerable 配置默认为 false
obj.d = 4; // 使用直接赋值的方式创建对象的属性,则这个属性的 enumerable 为 true

for (var i in obj) {
  console.log(i); // 打印 'a' 和 'd'
}

Object.keys(obj); // ["a", "d"]

obj.propertyIsEnumerable('a'); // true
obj.propertyIsEnumerable('b'); // false
obj.propertyIsEnumerable('c'); // false

Writable

writable 属性设置为 false 时,该属性不可写,它不能被重新赋值。

var obj = {};

Object.defineProperty(obj, 'a', {
  value: 37,
  writable: false,
});

console.log(obj.a); // 37
// 严格模式下会抛出错误,即使是赋的值与之前相等,如 obj.a = 37
obj.a = 25;
// the assignment didn't work
console.log(o.a); // 37

// strict mode
(function() {
  'use strict';

  var obj = {};
  Object.defineProperty(obj, 'b', {
    value: 2,
    writable: false,
  });
  obj.b = 3; // throws TypeError: "b" is read-only
  return obj.b; // returns 2
}());

Getter

get 表示如何获取属性的值。配置了该选项后,属性的值不能通过外界的赋值操作来更改,属性的值是通过 get 来获取的。

var obj = {};

Object.defineProperty(obj, 'a', {
  get() {
    return 'value of a'
  }
})

obj.a = 123
console.log(obj.a) // 'value of a'

Setter

set 表示对该属性进行赋值操作时的回调。配置了该选项后,对属性进行赋值,将会调用该属性配置下的 set 方法。

var obj = {};

Object.defineProperty(obj, 'a', {
  set() {
    console.log('set() has beend called')
  }
})

obj.a = 123; // log 'set() has beend called', and obj.a is undefined

默认行为

使用 . 运算符和 Object.defineProperty() 为对象的属性赋值时,数据描述符中的属性默认值是不同的,如下例所示:

var obj = {};

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


// 另一方面,
Object.defineProperty(obj, "a", { value : 1 });
// 等同于:
Object.defineProperty(obj, "a", {
  value: 1,
  writable: false,
  configurable: false,
  enumerable: false
});

Object.defineProperty 是 Vue 实现数据响应式的核心,还需要结合的是 watcher,有待研究。

Reference: Object.defineProperty | MDN

完。