前言

JavaScript ECMA5中给对象定义了一个属性描述符的概念,就是用于描述这个对象的属性特征,例如:是否可被枚举,删除或者属性值是什么等

对象属性描述符分两种:

  • 数据描述符
  • 存取描述符

一、数据描述符

查看对象的某个属性的数据描述符

使用Object.getOwnPropertyDescriptor方法

Object.getOwnPropertyDescriptor(object: Object, prop: String | Symbol);

let obj = {name: 'my name'};
let descriptor = Object.getOwnPropertyDescriptor(obj, 'name');
console.log(descriptor);

Object.getOwnPropertyDescriptor方法返回一个对象:

  • value 默认值:undefined – 表示的该属性目前值是什么
  • writable 默认值:false – 表示该属性是否可以被重新赋值
  • enumerable 默认值:false – 表示该属性是否可被枚举,可参考for..in 或者 Object.keys
  • configurable 默认值:false – 当为true时表示该属性描述符可以被更改,以此同时,该属性才可以被删除

设置对象某个属性的描述符

使用Object.defineProperty方法

Object.defineProperty(object: Object, prop: String | Symbol, descriptor: Object);

let obj = {name: 'my name'};
Object.defineProperty(obj, 'name', {
  ...
});

具体的每个描述符的意义

value

let obj = {name: 'my name'};
Object.defineProperty(obj, 'name', {
  value: 'change name'
});
console.log(obj.name); // 'change name'

从上面代码看出,此时的obj.name已经被更改

writable

let obj = {name: 'my name'};
Object.defineProperty(obj, 'name', {
  writable: false
});
obj.name = 'change name'; // 不报错,但是修改失败
console.log(obj.name); // 'my name'

enumerable

let obj = {name: 'my name'};
console.log(Object.keys(obj)); // ['name']
console.log(Object.getOwnPropertyDescriptor(obj, 'name').enumerable); // true

当属性描述符enumerabletrue是可以被枚举的,也就是可以使用for..in或者Object.keys方法遍历属性,但是当enumerable该描述符为false则不能被枚举,如下所示

let obj = {name: 'my name'};
Object.defineProperty(obj, 'name', {
  enumerable: false
});
console.log(Object.keys(obj)); // []

configurable

特征一:该属性描述符为false时不可更该属性描述符的其他特征,但是仅对enumerable有效

let obj = {name: 'my name'};
Object.defineProperty(obj, 'name', {
  configurable: false
});
Object.defineProperty(obj, 'name', {
  enumerable: false, // TypeError: Cannot redefine property: name
});

特征二:configurabletrue时,该属性才可以使用delete操作符或者Reflect.deleteProperty删除

let obj = {name: 'my name'};
Object.defineProperty(obj, 'name', {
  configurable: false
});
delete obj.name;
console.log(obj); // {name: "my name"} 无法删除
Reflect.deleteProperty(obj,'name'); // false

二、存取描述符

存取描述符有getset,默认值均为undefined,可以定义成函数,这两个描述符无法通过Object.getOwnPropertyDescriptor获取

get

属性的getter函数,当访问对象的属性时,会调用此函数。该属性默认值为undefined,则不会进行调用

let obj = {name: 'my name'};
Object.defineProperty(obj, 'name', {
  get: function() {
    return 'get name';
  }
});
console.log(obj.name); // get name

可以看到obj.name已经发生了变化,getter属性更像是一个钩子函数

需要注意的是,定义getter函数避免使用箭头函数,否则this将会发生改变

let obj = {name: 'my name'};
Object.defineProperty(obj, 'name', {
  get: () => {
    console.log(this); // window
    return 'get name';
  }
});
console.log(obj.name); // get name

set

属性的setter函数,当对象的属性被修改时,会调用此函数。这个函数会传入一个参数,即是被赋的新值,该属性默认值为undefined,则不会进行调用

let obj = {name: 'my name'};
let temp = undefined;
Object.defineProperty(obj, 'name', {
  get: function () {
    return temp || this.name;
  },
  set: function (newValue) {
    temp = newValue;
  }
});
obj.name = 'change name';
console.log(obj.name); // change name

注意:为什么上述代码中需要加入temp来存储新值呢,主要因为在setter函数里面直接更改该属性的值将进入死循环,因为setter会不断被触发,如下

let obj = {name: 'my name'};
Object.defineProperty(obj, 'name', {
  set: function (newValue) {
    console.log(newValue); // change name
    this.name = newValue;
  }
});
obj.name = 'change name'; // 进入死循环
console.log(obj.name);

同样定义setter函数避免使用箭头函数