目录

JS 中 Object.defineProperty 的使用方法

目录

1、Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象,语法如下:

Object.defineProperty(obj, prop, descriptor)

其中:

  • obj是需要被操作的目标对象
  • prop是目标对象需要定义或修改的属性的名称
  • descriptor是将被定义或修改的属性的描述符
  • 函数将返回被传递给函数的对象

2、一个简单的实例:

1
2
var o = {};
Object.defineProperty(o, "a", { value: 37 }); //{a: 37}

上述代码中,我们用Object.defineProperty为对象 o 创建的了一个新属性 a,它的值为 37,但是我们也发现了一些问题,参考如下代码:

1
2
3
4
5
var o = {};
Object.defineProperty(o, "a", { value: 37 }); //{a: 37}
console.log(o.a); // 打印 37
o.a = 25; // 没有错误抛出(在严格模式下会抛出,即使之前已经有相同的值)
console.log(o.a); // 打印 37, 赋值不起作用。

我们发现,对 o.a 赋值似乎不起作用,原来Object.defineProperty的第三个参数descriptor有很多属性描述符,其中就有是否能被赋值运算符改变 value的属性描述符。

3、具体的属性描述符如下:

  • configurable 当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false
  • enumerable 当且仅当该属性的 enumerable 为 true 时,该属性才能够出现在对象的枚举属性中**(可以被 for..in..遍历)**。默认为 false
  • value 该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined
  • writable 当且仅当该属性的 writable 为 true 时,该属性才能被赋值运算符改变。默认为 false
  • get 一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。该方法返回值被用作属性值。默认为 undefined
  • set 一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认为 undefined

4、这时候我们改造一下代码,如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var o = {};
Object.defineProperty(o, "a", {
  value: 37,
  writable: true,
  enumerable: true,
  configurable: true,
}); //{a: 37}
console.log(o.a); // 打印 37
o.a = 25;
console.log(o.a); // 打印 25

这样的话我们就可以正确地为属性赋值了。

5、需要注意的是,对象里目前存在的属性描述符有两种主要形式:数据描述符存取描述符。数据描述符是一个拥有可写或不可写值的属性。存取描述符是由一对 getter-setter 函数功能来描述的属性。**描述符必须是两种形式之一;不能同时是两者。**参考如下:

https://github.com/WangYuLue/pic_of_blog/blob/master/1808/1.png?raw=true

https://github.com/WangYuLue/pic_of_blog/blob/master/1808/2.png?raw=true

如果两者同时使用,会报如下错误:

1
2
3
4
5
6
7
8
var o = {};
Object.defineProperty(o, "conflict", {
  value: 0x9f91102,
  get: function () {
    return 0xdeadbeef;
  },
});
//Uncaught TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute

6、在这里,我们需要特别注意 configurable 这个属性描述符,如果它的值为false,那么除了 writable 外,其他特性都不能被修改(包括其自身),并且 writable 只能从 true 修改为 false,而且数据和存取描述符也不能相互切换。

如果尝试修改,会报如下错误:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var o = {};
Object.defineProperty(o, "a", {
  get: function () {
    return 1;
  },
  configurable: false,
});
// throws a TypeError
Object.defineProperty(o, "a", { configurable: true });
// throws a TypeError
Object.defineProperty(o, "a", { enumerable: true });
// throws a TypeError (set was undefined previously)
Object.defineProperty(o, "a", { set: function () {} });
// throws a TypeError (even though the new get does exactly the same thing)
Object.defineProperty(o, "a", {
  get: function () {
    return 1;
  },
});
// throws a TypeError
Object.defineProperty(o, "a", { value: 12 });

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

7、最后,我们又要留意一下存取描述符setget,vuejs 的底层就是通过setget监听数据变动来实现 mvvm 的双向绑定的,参考如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function observe(data) {
  if (!data || typeof data !== "object") {
    return;
  }
  // 取出所有属性遍历
  Object.keys(data).forEach(function (key) {
    defineReactive(data, key, data[key]);
  });
}

function defineReactive(data, key, val) {
  observe(val); // 监听子属性
  Object.defineProperty(data, key, {
    enumerable: true, // 可枚举
    configurable: false, // 不能再define
    get: function () {
      return val;
    },
    set: function (newVal) {
      console.log("哈哈哈,监听到值变化了 ", val, " --> ", newVal);
      val = newVal;
    },
  });
}

var obj = { a: "10", b: "20" };
observe(obj);
obj.a; // '10'
obj.a = "100";
// 哈哈哈,监听到值变化了  10  -->  100
// '100'

参考文档 MDN web docs

Snandy 的博客