blog
blog copied to clipboard
JavaScript 的 Object
目录
-
Object 的属性描述符
- 描述符:
-
enumerable
- 可枚举 + 不可枚举
- String属性 + Symbol属性
- 自身属性 + 原型属性
-
configurable
-
value
,writable
数据描述符 -
get
,set
访问器描述符
-
- 相关方法
-
Object.defineProperty()
-
Object.getOwnPropertyDescriptor()
-
Object.getOwnPropertyDescriptors()
-
- 描述符:
一. Object 的属性描述符
在 JavaScript 中, 对象(Object)可以看作是属性(property)的集合,属性是 key:value
的形式。key
可以是字符串也可以是 Symbol,value
可以是任何类型(包括其它对象)。
对象的每个属性(property)都有对应的 attributes(属性),由于 attributes 是在 JavaScript 引擎内部使用的,所以我们并不能直接访问到它们。为了区分两者,通常情况下 [property]
是用单方括号,[[attribute]]
是用双方括号。
有一部分特殊的 attributes 是 property 的描述符,我们可以在 Object.defineProperty()
方法中直观地看到,此方法允许我们精准地添加或修改对象的属性及其描述符。
Object.defineProperty()
静态方法 Object.defineProperty()
可以在对象上定义新的属性,也可以修改已有的属性,它会返回本对象。
格式:
Object.defineProperty(obj, prop, descriptor)
两种描述符
对象中的属性描述符有两种形式:数据描述符和访问器描述符。数据描述符是具有值的属性(可写或不可写),访问器描述符是由一对 getter-setter 函数描述的属性。
data descriptors
accessor descriptors
属性描述符要么是数据描述符,要么是访问器描述符,不能两者兼而有之。
数据描述符和访问器描述符都是对象,即 key:value
的集合,它们都有下面的两个 key:
-
enumerable
布尔类型。当且仅当在枚举对象的属性列表时,该属性需要被显示出来的时候,才会被置为 true。 -
configurable
布尔类型。如果可以更改该属性的描述符类型(数据描述符 or 访问器描述符),且可以在对象上删除该属性的时候,就置为 true。
数据描述符还可以有以下 key:
-
value
与属性关联的值,可以是任何有效的 JavaScript 值。 -
writable
布尔类型。如果可以使用赋值运算符来修改该属性的value
,那就置为 true。
访问器描述符还可以有以下 key:
-
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'
});
默认值
用 Object.defineProperty()
定义的描述符的默认值分别是:
-
enumerable
,configurable
,writable
均默认是 false -
value
,get
,set
均默认是 undefined
也就是说,通过此方法添加的属性,默认是不可枚举、不可变的。而通过赋值添加的普通属性,默认是可枚举、可删除可修改的。
let person = {
name: "David"
};
person.age = 34;
Object.defineProperty(person, "sex", { value: "male" });
console.log(Object.getOwnPropertyDescriptors(person)); // 详见下方截图
以上代码,运行结果如下:

enumerable
是否可枚举
可枚举属性就是指该属性的 enumerable
描述符为 true 的属性。当我们说一个属性是可枚举的,就意味着:
-
Object.assign()
和 spread 运算符...
能访问到-
Object.assign()
会将所有可枚举的自身属性从一个或多个源对象复制到目标对象,然后返回修改后的目标对象。它在源对象上使用[[Get]]
,在目标对象上使用[[Set]]
,它调用的是 getter 和 setter,因此它是分配(assign)属性而不是复制或定义新属性。- 如果还想复制属性的描述符,可以使用
Object.defineProperty()
和Object.getOwnPropertyDescriptor()
。
- 如果还想复制属性的描述符,可以使用
-
...
对于对象字面量,会将自己的可枚举属性复制到新对象- 现在可以使用比
Object.assign()
更短的语法进行对象的浅克隆或合并(不包含原型链) -
Object.assign()
会触发 setter,而...
不会
- 现在可以使用比
-
- 对于非 Symbol 属性,可枚举的属性能出现在
for...in
循环和Object.keys()
中。
方法 | 自身属性 | 原型链 | |
---|---|---|---|
检测 | propertyIsEnumerable() |
✔️ 判断是否可枚举(String 和 Symbol) | |
hasOwnProperty() |
✔️ 可枚举+不可枚举(String 和 Symbol) | ||
in 操作符 |
✔️ 可枚举+不可枚举(String 和 Symbol) | ✔️ 同自身 | |
检索 | Object.getOwnPropertyDescriptors() Reflect.ownKeys() |
✔️ 可枚举+不可枚举(String 和 Symbol) | |
Object.getOwnPropertyNames() Object.getOwnPropertySymbols() |
✔️ 可枚举+不可枚举(仅 String 属性) ✔️ 可枚举+不可枚举(仅 Symbol 属性) |
||
Object.keys() |
✔️ 可枚举(仅 String 属性) | ||
迭代/枚举 | for..in 语句 |
✔️ 可枚举(仅 String 属性) | ✔️ 同自身 |
- detecting object properties
Object.prototype.propertyIsEnumerable()
Object.prototype.hasOwnProperty()
- retrieving .. ..
- iterating/enumerating .. ..
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));
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"; }
});
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>'
})();
看几个例子
eg1. 普通函数对象
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);
eg2. 带继承的(原型链)
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 black
总结
本文重点介绍了对象属性的描述符,内容如下:
描述符 | key | 说明 | 反转状态 |
---|---|---|---|
数据描述符 访问器描述符 |
enumerable configurable |
是否可枚举 是否可更改描述符类型+删除 |
DontEnum DontDelete |
数据描述符 | value writable |
属性的值 是否可修改值(赋值) |
Read-only |
访问器描述符 | get set |
扩展阅读
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties