前言
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
当属性描述符enumerable
为true
是可以被枚举的,也就是可以使用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
});
特征二:configurable
为true
时,该属性才可以使用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
二、存取描述符
存取描述符有get
和set
,默认值均为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
函数避免使用箭头函数