前言
创建对象有许多方式,最常用的就是使用字面量的方式来创建,如下
js
let obj = {
name: "obj",
age: 18,
};
但如果要精准控制对象的每个属性,比如增加一个属性height
,这个属性是否可以删除、修改、遍历等,只靠对象字面量这种方式创建就没办法做到了,于是要引出今天的主题Object.defineProperty
Object.defineProperty 详讲
语法规则
Object.defineProperty(obj, property, propertyDescriptorMap)
- obj: 要定义属性的对象。
- property : 一个字符串或
Symbol
,指定了要定义或修改的属性键。 - propertyDescript : 要定义或修改的属性的描述符。
返回值与参数一obj指向相同,所以一般不会有特定的变量来接收。
属性描述符
Object.defineProperty
第三个参数为属性描述符,属性描述符分两种:数据属性描述符 和 访问器属性描述符
但是不论数据属性描述符还是访问器属性描述符都会有两个通用的可选键,configerable
和enumerable
,在此我先把这两个可选键统称为通用属性描述符 。
数据属性描述符
数据属性描述符主要来控制对象属性是否可写 ,其有两个键writable
和value
。
writable
:控制对象属性是否可修改,默认值为false
。value
:对象属性的值,默认值为undefined
js
let obj = {
name: "obj",
age: 18,
};
Object.defineProperty(obj, "height", {
writable: false,
value: 1.85,
});
obj.height = 2
console.log(obj.height); //1.85
上面的案例可以看出,如果writable
为false
则不能对height属性进行修改,如果放在严格模式下修改height属性值则会报错。
访问器属性描述符
访问器属性描述符是由 getter/setter
函数对描述的属性,其有两个键get
和set
,这两个健对应的值都为函数类型。
get
:读取对象属性时的拦截器,只要读取属性则会调用该函数,函数返回值被用作该属性的值。set
:设置对象属性时的拦截器,只要给属性赋值则会调用该函数,并把值传递给该函数。
js
function Obj() {
let height = null;
Object.defineProperty(this, "height", {
get: function () {
console.log("对象height属性被读取");
return height;
},
set: function (value) {
console.log("对象height属性被设置");
height = value;
},
});
}
let newObj = new Obj();
newObj.height = 1.9; //log:对象height属性被设置
console.log(newObj.height);
/*log:对象height属性被读取 1.9*/
我觉得上面案例应该不需要解释什么,如果换成下面的案例会发生什么?
js
let obj = {
name: "obj",
};
Object.defineProperty(obj, "height", {
get: function () {
return obj.height;
},
set: function (val) {
obj.height = val;
},
});
obj.height = 2;
首先对obj.height
赋值会调用setter,而setter里又会对obj.height
赋值,结果又会调用了setter....一直循环下去,变成了死循环。
^v^ 这是我依然会犯的错误
通用属性描述符
通用属性描述符包含两种configurable
和enumerable
configurable
:表示属性是否可以删除 属性和是否可以再次配置属性描述符enumerable
:是否可以遍历该属性,如果为false
,打印obj对象则不会显示该属性。使用Object.keys
也不会显示该属性。使用Object.values
也不会获取到对应的值。但该属性是真实存在的,只能通过对象.属性
来获取该值。
js
let obj = {
name: "obj",
};
Object.defineProperty(obj, "height", {
configurable: false,
value: 2,
});
delete obj.height;
console.log(obj.height);
Object.defineProperty(obj, "height", {
configurable: false,
writable: true,
value: 2,
});
obj.height = 3; // 异常:Uncaught TypeError: can't redefine non-configurable property "height"
上面案例可以看出如果给属性的configurable
设置为false
,则该属性删除是没有效果的,如果在严格模式下则会报错,接下来我们又打算对height
属性重新定义属性描述符,发现会报错,不能再次给height
属性配置属性描述符。
js
let obj = {
name: "obj",
};
Object.defineProperty(obj, "height", {
enumerable:false,
value: 2,
});
console.log(Object.keys(obj)); //Array [ "name" ]
console.log(Object.values(obj)); //Array [ "obj" ]
console.log(obj.height); //2
该案例展示了height
为不可遍历状态
属性描述符注意事项
数据属性描述符和访问器属性描述符不可以同时存在。也就是value
和writable
不能与get
和set
同时出现,但可以与通用属性描述符同时出现。
configurable | enumerable | writable | value | get | set | |
---|---|---|---|---|---|---|
数据属性描述符 | 可以 | 可以 | 可以 | 可以 | 不可以 | 不可以 |
访问器属性描述符 | 可以 | 可以 | 不可以 | 不可以 | 可以 | 可以 |
思考
js
let obj = {
name: "obj",
};
字面量定义的对象,obj.name
都有什么属性描述符?
可以通过Object.getOwnPropertyDescriptor(obj, "name")
输出看下。