谈谈JS中的Object.defineProperty()

3,517 阅读4分钟

今天和大家来聊一聊Object.defineProperty这个方法,学过vue的人都知道,vue的劫持原理就是通过该方法进行数据拦截的,如果想要深入了解vue的原理,Object.defineProperty是避免不了的。

首先来介绍一下语法

定义

Object.defineProperty的作用是在一个对象上定义一个新属性,或对已有属性进行修改。

用法

/**
 * @param {Object} target 需要定义/修改属性的目标对象
 * @param {String} propName  需要定义/修改的属性名称
 * @param {Object} descriptor 需要定义/修改的属性描述符
 */
Object.defineProperty(target, propName, descriptor);

target和name我就不多解释了,详细的来解释一下desc。

描述符

Object.defineProperty()的描述符分为两大类: 第一类叫数据描述符,专门用来对数据进行限制说明的,比如是否可修改,是否可枚举(遍历),是否可删除/修改等。 第二类叫存储器描述符(访问器描述符),用于对数据的获取和修改进行说明的,比如当我这个数据调用时我需要返回什么样的数据,该数据修改时需要按照什么样的逻辑修改等。 数据描述符有4个,分别是value,writable,enumerable,configurable。 操作描述符有2个,分别是get和set。

value

value是需要定义/修改的属性的值,或者说我更喜欢将其叫做默认值。

Object.defineProperty(window, "name", {
    value: '张三'
});
console.log(window.name); // '张三'

比如我需要在全局属性window对象下定义一个名字叫做name的属性,我可以用上述代码,这段代码等价于var name = '张三';但又有一点区别,不要急,我接着讲,大家接着看,我等会告诉大家区别在哪。

writable

writable决定着该属性是否可以被重新赋值,用Object.defineProperty()定义时默认为false,用var name = '张三'表达式定义时默认为true。

Object.defineProperty(window, "name", {
    value: '张三'
});
window.name = '李四';
console.log(window.name); // 张三

Object.defineProperty(window, "age", {
    value: 15,
    writable: true
});
window.age = 20;
console.log(window.age); // 20

参考上边两个栗子,我们可以看到,默认情况下该属性不可修改,当将writable设置为true时,该属性输出了我们新输入的值。

var name = '张三';
name = '李四';
console.log(name); // 李四

我们可以联想到,平时定义一个变量时是可以随时修改的,那么我们可以大胆猜想,表达式定义的属性默认的writable是true,这也是上面遗留问题的区别之一。验证一下。

Object.defineProperty(window, "name", {
    value: '张三'
});
var age = 12;
console.log(Object.getOwnPropertyDescriptor(window, 'name')); // {value: "张三", writable: false, enumerable: true, configurable: true}
console.log(Object.getOwnPropertyDescriptor(window, 'age')); // {value: 12, writable: true, enumerable: true, configurable: false}

用Object.getOwnPropertyDescriptor()可以查看描述符的具体值,这个语法下面我会详细介绍,现在不用太纠结,只要知道该方法可以获取该值描述符的具体值就行。 我们可以看到,用var定义的属性的writable确实是true,而用Object.defineProperty定义的是false。这里我们是不是可以联想到常量,常量的定义是不是也可以通过该描述符进行定义呢?这个留给大家思考,这里不详细介绍了。

enumerable

enumerable决定该属性的值是否可以被枚举,也就是我们所说的遍历,true为可以被枚举,false为不可以,在Object.defineProperty()中定义的属性默认false,表达式格式定义的默认为true。

var obj = {a: 1, b: 2}
Object.defineProperty(obj, 'c', {
  value: 3,
  enumerable:false
});
console.log(obj); // {a: 1, b: 2, c: 3}
for (let key in obj) {console.log(key)}; // a, b
console.log(Object.keys(obj)); // ['a', 'b']

我们看到,当我们将enumerable设置为false时,无论用for...in还是Object.keys(),都无法获取到c属性,当我们将enumerable设置为true时可以获取到,这里就不验证了,大家自行验证。

configurable

configurable决定着目标属性是否可以被删除或者已经设置的描述符是否可以修改,默认为false。

var a = 100;
delete a;
console.log(a); // 100

var obj = {};
Object.defineProperty(obj, 'a', {
  value: 100
});
delete obj.a;
console.log(obj.a); // 100

get

get是用于获取该属性值的方法。

var obj = {};
Object.defineProperty(obj, 'a', {
  get () {
    return 100;
  }
});
console.log(obj.a); // 100

注意,get不能和configurable共存,否则会报错。

set

set是用于修改该属性值的方法。

var obj = {};
var value = 100;
Object.defineProperty(obj, 'a', {
  get () {
    return value;
  },
  set (val) {
    value = val;
  }
});
console.log(obj.a); // 100
obj.a = 99;
console.log(obj.a); // 99

注意,set和get一样,不能和configurable共存,否则会报错。

总结

1、value:需要定义/修改的属性的默认值

2、writable:决定着值是否可以被修改

3、enumerable:决定着值是否可以被枚举

4、configurable:决定着值是否可以被删除或其他描述符是否可以修改

5、get:用于获取该属性的值,不能和configurable共存

6、set:用于设置该属性的值,不能和configurable共存