❗属性描述符
针对 Object 的 property,数据描述符和访问器描述符
在 JavaScript 中, 对象(Object)可以看作是属性(property)的集合,属性是 key:value 的形式。key 可以是字符串也可以是 Symbol,value 可以是任何类型(包括其它对象)。
对象的每个属性(property)都有对应的 attributes(属性),由于 attributes 是在 JavaScript 引擎内部使用的,所以我们并不能直接访问到它们。为了区分两者,通常情况下 [property] 是用单方括号,[[attribute]] 是用双方括号。
有一部分特殊的 attributes 是 property 的描述符,我们可以在静态方法 Object.defineProperty() 中直观地看到,它允许我们精准地添加和修改对象的属性及其描述符。此方法可以在对象上定义新的属性,也可以修改已有的属性,格式是 Object.defineProperty(obj, prop, descriptor)。
1. 两种描述符
对象中的属性描述符有两种形式:数据描述符和访问器描述符。
data descriptors,数据描述符是具有值的属性(可写或不可写)
accessor descriptors,访问器描述符是由一对 getter-setter 函数描述的属性
属性描述符要么是数据描述符,要么是访问器描述符,不能两者兼而有之。
不论是数据描述符还是访问器描述符,它们都是对象,即 key:value 集合。
数据描述符
enumerable
configurable
value
writable
访问器描述符
enumerable
configurable
get
set
1.1 描述符的 key
enumerable布尔类型。当且仅当在枚举对象的属性列表时该属性需要被显示出来的时候,才会被置为 true。configurable布尔类型。如果可以更改该属性的描述符类型(数据描述符 or 访问器描述符),且可以在对象上删除该属性的时候,就置为 true。value与属性关联的值,可以是任何有效的 JavaScript 值。writable布尔类型。如果可以使用赋值运算符来修改该属性的value,那就置为 true。get用作属性的 getter 函数,如果没有则为 undefined。当访问该属性的值时,就会调用此函数(不带参数),并将this设置为访问该属性的对象,返回值将被用作是该属性的值。set用作属性的 setter 函数,如果没有则为 undefined。当设置(assign)该属性的值时,就会调用此函数(带一个参数,即分配给属性的值),并将this设置为给该属性分配值的对象。
如果描述符既没有 value, writable 也没有 get, set,就会将其视为数据描述符。如果描述符既有 value/writable 也有 get/set,则会抛出异常。
注意:这些 attributes 不一定是描述符自身的 properties,也可能是继承来的 properties。所以保险起见,要么使用 Object.create(null) 让描述符指向 null,要么使用对象字面量来显式指定描述符的值。如下:
// 1. Object.create(null)
let obj = {};
let descriptor = Object.create(null); // no inherited properties
descriptor.value = 'static';
Object.defineProperty(obj, 'key1', descriptor);
// 2. 用对象字面量,显式指定
Object.defineProperty(obj, 'key2', {
enumerable: false,
configurable: false,
writable: false,
value: 'static'
});1.2 描述符的默认值
用 Object.defineProperty() 定义的描述符,默认值分别是:
enumerable,configurable,writable均默认是 falsevalue,get,set均默认是 undefined
也就是说,通过此方法添加的属性,默认是不可枚举、不可变的。
而通过赋值添加的普通属性,默认是可枚举、可删除可修改的。
let person = {
name: "David"
};
person.age = 34;
Object.defineProperty(person, "sex", { value: "male" });
console.log(Object.getOwnPropertyDescriptors(person)); // 详见下方截图以上代码,运行结果如下:

2. 重点介绍
2.1 enumerable
可枚举属性,是指该属性的 enumerable 描述符为 true 的属性。
当我们说一个属性是可枚举的,就意味着:
对于非 Symbol 属性,可枚举的属性能出现在
for...in循环和Object.keys()中Object.assign()和 spread 运算符...能访问到Object.assign()会将所有可枚举的自身属性从一个或多个源对象复制到目标对象,然后返回修改后的目标对象。它在源对象上使用[[Get]],在目标对象上使用[[Set]],它调用的是 getter 和 setter,因此它是分配(assign)属性而不是复制或定义新属性。如果还想复制属性的描述符,可以使用
Object.defineProperty()和Object.getOwnPropertyDescriptor()。
...对于对象字面量,会将自己的可枚举属性复制到新对象现在可以使用比
Object.assign()更短的语法进行对象的浅克隆或合并(不包含原型链)Object.assign()会触发 setter,而...不会
propertyIsEnumerable()
判断是否可枚举 (String+Symbol)
❌
hasOwnProperty()
Object.getOwnPropertyDescriptors()
Reflect.ownKeys()
可枚举+不可枚举 (String+Symbol)
❌
Object.getOwnPropertyNames()
可枚举+不可枚举 (仅 String)
❌
Object.getOwnPropertySymbols()
可枚举+不可枚举 (仅 Symbol)
❌
Object.keys()
可枚举 (仅 String 属性)
❌
for..in 语句
可枚举 (仅 String 属性)
同前
in 操作符
可枚举+不可枚举 (String+Symbol)
同前
detecting object properties, 检测
Object.prototype.propertyIsEnumerable()Object.prototype.hasOwnProperty()in操作符
retrieving ... ..., 检索
Object.getOwnPropertyDescriptors()Reflect.ownKeys()Object.getOwnPropertyNames()Object.getOwnPropertySymbols()Object.keys()
iterating/enumerating ... ..., 迭代/枚举
for..in语句
let obj = {
1: "111"
};
obj["2"] = "222";
Object.defineProperty(obj, "3", { value: "333" });
Object.defineProperty(obj, "4", {
value: "333",
enumerable: true
});
Object.defineProperty(obj, "5", {
get() { return "555" }
});
Object.defineProperty(obj, "6", {
get() { return "666" },
enumerable: true
});
const mySymbol = Symbol("7");
obj[mySymbol] = "777";
const mySymbol2 = Symbol("8");
Object.defineProperty(obj, mySymbol2, { value: "888" });
obj["9"] = "999";
console.log(Object.keys(obj)); // ['1', '2', '4', '6', '9']
console.log(Object.getOwnPropertyNames(obj)); // ['1', '2', '3', '4', '5', '6', '9']
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(7), Symbol(8)]
for (let i in obj) {
console.log(i); // 1, 2, 4, 6, 9
}
console.log(obj.propertyIsEnumerable(1)); // true
console.log(obj.propertyIsEnumerable(mySymbol)); // true
console.log(obj.propertyIsEnumerable(mySymbol2)); // false
console.log(obj);
console.log(Object.getOwnPropertyDescriptors(obj));2.2 configurable
是否可以更改该属性的描述符类型(数据/访问)
是否可以在对象上删除该属性
let person = {};
Object.defineProperty(person, "job", {
get() { return "programmer"; },
configurable: true
});
Object.defineProperty(person, "play", {
value: "football",
configurable: false
});
Object.defineProperty(person, "child", {
configurable: true
});
delete person.job; // 会生效
delete person.play; // 不会生效
Object.defineProperty(person, "child", { // 会生效
get() { return "Lily"; }
});2.3 writable
是否可以使用赋值运算符修改该属性的 value。
eg. 当属性不可写时
let person = {};
Object.defineProperty(person, "sex", {
value: "male",
writable: false
});
console.log(person.sex); // "male"
person.sex = "female"; // No error thrown
console.log(person.sex); // "male"eg. 当属性不可写时,严格模式下会报错
(function() {
"use strict";
let person = {};
Object.defineProperty(person, "sex", {
value: "male",
writable: false
});
console.log(person.sex);
person.sex = "female"; // TypeError: Cannot assign to read only property 'sex' of object '#<Object>'
})();3. 例子
3.1 普通函数对象
function Person(name, initSalary) {
let salary = initSalary;
Object.defineProperty(this, "name", { value: name });
Object.defineProperty(this, "sex", {
value: "male",
writable: false,
enumerable: true,
configurable: false
});
Object.defineProperty(this, "salary", {
get() {
return salary;
},
set(newValue) {
salary = newValue;
},
enumerable: true,
configurable: true
});
}
let person1 = new Person('David', 1000);
let person2 = new Person('John', 2000);3.2 原型链
function Person() { }
// 访问器属性,需要存在新定义的变量上,否则就在原型链上(所有后代共享)
Object.defineProperty(Person.prototype, "eat", {
get() {
return this._eat;
},
set(val) {
this._eat = val;
}
});
// 数据属性,本就在对象自身上(而不在原型链上)
Object.defineProperty(Person.prototype, "hair", {
value: "black",
writable: true
});
let p1 = new Person();
let p2 = new Person();
// 访问器属性
console.log(p1.eat, p2.eat); // undefined undefined
p1.eat = "rice";
console.log(p1.eat, p2.eat); // rice undefined
// 数据属性
console.log(p1.hair, p2.hair); // black black
p1.hair = "red";
console.log(p1.hair, p2.hair); // red black4. 总结
这部分重点介绍了对象属性的描述符,内容如下:
enumerable
configurable
是否可枚举 是否可更改描述符类型+删除
DontEnum
DontDelete
writable
value
是否可修改值(赋值)
属性的值
Read-only
get
set
5. 扩展阅读
Last updated