本文为【JavaScript 漫游】专栏的第 010 篇文章,记录了属性描述对象的重要知识点。
Object.getOwnPropertyDescriptor
Object.getOwnPropertyNames
Object.defineProperty
、Object.defineProperties
Object.prototype.propertyIsEnumerable
什么是属性描述对象
JS 提供了一个内部数据结构,用来描述对象的属性,控制它的行为,比如该属性是否可写、可遍历等等。这个内部数据结构称为 属性描述对象。每个属性都有自己对应的属性描述对象,保存该属性的一些元信息。
下面是属性描述对象的一个例子。
{
value: 123,
writable: false,
enumerable: true,
configurable: false,
get: undefined,
set: undefined
}
属性描述对象提供 6 个元属性。
value
是该属性的属性值,默认为 undefined
writable
是一个布尔值,表示属性值是否可改变,默认为 true
enumerable
是一个布尔值,表示该属性是否可遍历,默认为 true
。如果设为 false
,会使得某些操作(比如 for...in
循环、Object.keys
方法)跳过该属性。configurable
是一个布尔值,表示可配置项,默认为 false
。如果设为 false
,比如无法删除该属性,也不得改变该属性的属性描述对象(value
属性除外)。也就是说,configurable
属性控制了属性描述对象的可写性。get
是一个函数,表示该属性的取值函数(getter),默认为 undefined
set
是一个函数,表示该属性的存值函数(setter),默认为 undefined
Object.getOwnPropertyDescriptor
方法
Object.getOwnPropertyDescriptor
方法可以获取属性描述对象。它的第一个参数是目标对象,第二个参数是一个字符串,对应目标对象的某个属性名。
var obj = { p: 'a' };
Object.getOwnPropertyDescriptor(obj, 'p')
// Object { value: "a",
// writable: true,
// enumerable: true,
// configurable: true
// }
值得注意的是,Object.getOwnPropertyDescriptor
方法只能用于对象自身的属性,不能用于继承的属性。
var obj = { p: 'a' };
Object.getOwnPropertyDescriptor(obj, 'toString')
// undefined
Object.getOwnPropertyNames
方法
Object.getOwnPropertyNames
方法返回一个数组,成员是参数对象自身的全部属性的属性名,不管该属性是否可遍历。
var obj = Object.defineProperties({}, {
p1: { value: 1, enumerable: true },
p2: { value: 2, enumerable: false }
});
Object.getOwnPropertyNames(obj)
// ["p1", "p2"]
Object.defineProperty
和 Object.defineProperties
方法
Object.defineProperty
方法允许通过属性描述对象,定义或修改一个属性,然后返回修改后的对象。
Object.defineProperty(object, propertyName, attributesObject)
Object.defineProperty
方法接受三个参数,依次如下。
object
:属性所在的对象propertyName
:字符串,键名attributesObject
:属性描述对象var obj = Object.defineProperty({}, 'p', {
value: 123,
writable: false,
enumerable: true,
configurable: false
});
obj.p // 123
obj.p = 246;
obj.p // 123
如果一次性定义或修改多个属性,可以使用 Object.defineProperties
方法。
var obj = Object.defineProperties({}, {
p1: { value: 123, enumerable: true },
p2: { value: 'abc', enumerable: true },
p3: { get: function () { return this.p1 + this.p2 },
enumerable:true,
configurable:true
}
});
obj.p1 // 123
obj.p2 // "abc"
obj.p3 // "123abc"
Object.prototype.propertyIsEnumerable
方法
返回一个布尔值,用来判断某个函数是否可遍历。注意,这个方法只能用于判断对象自身的属性,对于继承的属性一律返回 false
。
var obj = {};
obj.p = 123;
obj.propertyIsEnumerable('p') // true
obj.propertyIsEnumerable('toString') // false
元属性
属性描述对象的各个属性称为 元属性,因为它们可以看作是控制属性的属性。
value
属性是目标属性的值。
var obj = {};
obj.p = 123;
Object.getOwnPropertyDescriptor(obj, 'p').value
// 123
Object.defineProperty(obj, 'p', { value: 246 });
obj.p // 246
writable
属性是一个布尔值,决定了目标属性的值(value)是否可以被改变。
var obj = {};
Object.defineProperty(obj, 'a', {
value: 37,
writable: false
});
obj.a // 37
obj.a = 25;
obj.a // 37
enumerable
返回一个布尔值,表示目标属性是否可遍历。如果一个属性的 enumerable
为 false
,下面三个操作不会取到该属性。
for...in
循环Object.keys
方法JSON.stringify
方法var obj = {};
Object.defineProperty(obj, 'x', {
value: 123,
enumerable: false
});
obj.x // 123
for (var key in obj) {
console.log(key);
}
// undefined
Object.keys(obj) // []
JSON.stringify(obj) // "{}"
configurable
返回一个布尔值,决定了是否可以修改属性描述对象。当它的值为 false
时,value
、writable
、enumerable
和 configurable
都不能被修改了。
var obj = Object.defineProperty({}, 'p', {
value: 1,
writable: false,
enumerable: false,
configurable: false
});
Object.defineProperty(obj, 'p', {value: 2})
// TypeError: Cannot redefine property: p
Object.defineProperty(obj, 'p', {writable: true})
// TypeError: Cannot redefine property: p
Object.defineProperty(obj, 'p', {enumerable: true})
// TypeError: Cannot redefine property: p
Object.defineProperty(obj, 'p', {configurable: true})
// TypeError: Cannot redefine property: p
存取器
除了直接定义之外,属性还可以用存取器(accessor)定义。其中,存值函数称为 setter
,使用属性描述对象的 set
属性;取值函数称为 setter
,使用属性描述对象的 get
属性。
一旦对目标属性定义了存取器,那么存取的时候,都将执行对应的函数。利用这个功能,可以实现许多高级特性,比如定制属性的读取和赋值行为。
var obj = Object.defineProperty({}, 'p', {
get: function () {
return 'getter';
},
set: function (value) {
console.log('setter: ' + value);
}
});
obj.p // "getter"
obj.p = 123 // "setter: 123"
JS 还提供了存取器的另一种写法,并且这种写法更常用。
// 写法二
var obj = {
get p() {
return 'getter';
},
set p(value) {
console.log('setter: ' + value);
}
};