深入探究Object.defineProperty

449 阅读2分钟

一般情况下,我们给对象添加属性值都是这样子的:

let obj = {};
obj.key = 123;

其实,还有一种添加属性的方式是:Object.defineProperty(obj, prop, descriptor),这个API非常重要,它是VUE等框架实现数据响应式的基石。利用它设置的访问器属性可以做到数据劫持,然后再通过订阅/发布模式将数据的变化反应到视图上。

不扯远了,本文先来单纯地探究一下 Object.defineProperty

定义属性

let obj = {};
Object.defineProperty(obj, "key", {
  configurable: false,
  enumerable: false,
  writable: false,
  value: 123
});

我们称 configurable、enumerable、writable、value 为属性的四个描述符(即descriptor),也可以称它们为属性的四个特性:

configurable

当且仅当该属性的 configurable 为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。例子:
// 为true时可以改变其他描述符,也可以删除这个属性
let obj = {};
Object.defineProperty(obj, "key", {
  configurable: true,
  enumerable: false,
  writable: false,
  value: 123
});
Object.defineProperty(obj, "key", {
  enumerable: true
});

// 为false时不可以改变其他描述符,也不能删除这个属性
let obj = {};
Object.defineProperty(obj, "key", {
  configurable: false,
  enumerable: false,
  writable: false,
  value: 123
});
Object.defineProperty(obj, "key", {enumerable: true});      //报错
delete obj.key;     // false

enumerable

当且仅当该属性的enumerable为true时,该属性才能被枚举。
// 为true时属性可以被枚举
let obj = {};
Object.defineProperty(obj, "key", {
  configurable: true,
  enumerable: true,
  writable: false,
  value: 123
});
for (var i in obj) {    
  console.log(i);       // key
}

// 为false时属性可以不被枚举
let obj = {};
Object.defineProperty(obj, "key", {
  configurable: true,
  enumerable: false,
  writable: false,
  value: 123
});
for (var i in obj) {    
  console.log(i);      // undefined 
}

writable

当且仅当该属性的writable为true时,value才能被赋值运算符改变。
// 为true时属性可以被改变
let obj = {};
Object.defineProperty(obj, "key", {
  configurable: true,
  enumerable: true,
  writable: true,
  value: 123
});
obj.key = 456;
console.log(obj.key);       // 456

// 为false时属性不可以被改变
let obj = {};
Object.defineProperty(obj, "key", {
  configurable: true,
  enumerable: true,
  writable: false,
  value: 123
});
obj.key = 456;
console.log(obj.key);       // 123

value

该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)

注意,如果用 Object.defineProperty 定义属性时没有主动设置描述符,那么所有的描述符的值为false。

我们用 Object.getOwnPropertyDescriptor(obj, "key") 来获取属性的描述符信息

// 例1
let obj = {};
Object.defineProperty(obj, "key", {}); 
Object.getOwnPropertyDescriptor(obj, "key");
<!--
    {
    configurable: false,
    enumerable: false,
    writable: false,
    value: undefined
}
-->

// 例2
let obj = {};
Object.defineProperty(obj, "key", {value: 123}); 
Object.getOwnPropertyDescriptor(obj, "key");
<!--
    {
    configurable: false,
    enumerable: false,
    writable: false,
    value: 123
}
-->

// 例3
let obj = {};
Object.defineProperty(obj, "key", {writable: true}); 
Object.getOwnPropertyDescriptor(obj, "key");
<!--
    {
    configurable: false,
    enumerable: false,
    writable: true,
    value: undefined
}
-->

修改属性

对于一个已经存在的属性,它的默认描述符都为true。 因为与上文中的注意相悖,所以此处极容易混淆

let obj = {key: 123};
Object.getOwnPropertyDescriptor(obj, "key");
<!--
    {
    configurable: true,
    enumerable: true,
    writable: true,
    value: 123
}
-->
// 可以修改
Object.defineProperty(obj, "key", {writable: false});
Object.getOwnPropertyDescriptor(obj, "key");
<!--
    {
    configurable: true,
    enumerable: true,
    writable: false,
    value: 123
}
-->