对象用来存储属性。
直到现在,属性对我们来说就是简单“键:值
”对的形式。但对象属性还可以更加灵活和强大。
本节我们将引入“属性描述符”的概念,介绍了“数据属性”。而在下一章中,则会介绍另一种属性——“访问器属性”。
属性标记
对象属性,除了一个 value
,还有 3 个特别的属性(或叫标记,英文单词 flags)。
writable
:如果为true
,表示属性值可修改;否则是只读的。enumerable
:如果为true
,表示属性可枚举,能被循环遍历(比如for...in
);否则不能被循环遍历。configurable
:如果为true
,表示属性能被删除(即可以这么操作delete obj.foo
),属性标记可被修改;否则既不能删除属性,也不可修改属性标记。
你可能懵逼了,心想“之前没看到过啊”。当然啦,这些可不是一般人能看到的,如果你是使用“通常的方式”创建属性的,肯定不知道原来一个属性里还有这么多东西的。当我们用上面所说的“通常的方式”创建属性的时候,这些标记的默认值都是 true
,当然,这些标记值我们是可以修改的。
在此之前,先来看下如果得到这些标记。
答案是使用 Object.getOwnPropertyDescriptor
得到关于一个属性的所有信息。
此方法的语法如下:
let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);
propertyName
表示获取的是哪个属性obj
是获取属性的源对象。对应到这里,我们是要获取obj.``propertyName
的属性信息。
此方法的返回值是一个对象,被称为“属性描述符”:其中包含一个 value
,还有所有标记。
举个例子:
let user = {
name: "John"
};
let descriptor = Object.getOwnPropertyDescriptor(user, 'name');
alert( JSON.stringify(descriptor, null, 2 ) );
/* property descriptor:
{
"value": "John",
"writable": true,
"enumerable": true,
"configurable": true
}
*/
修改属性标记,我们使用的是 Object.defineProperty
。
语法如下:
Object.defineProperty(obj, propertyName, descriptor)
obj
、propertyName
:跟getOwnPropertyDescriptor
方法参数意思一样。不过这里不是在获取属性,而是设置属性了。descriptor
:这个就是描述符对象了。因为是设置属性,那么这个描述符就是用来描述obj.propertyName
这个属性的。
defineProperty
方法还有个特点:如果设置的属性是已经存在的,那么这个方法做的就是更新属性标记的操作;否则,就使用给定的标识符对象创建属性。另外还需要说明的是,创建属性时,没有指明的标记,默认都当做 false
设置。
举个例子。下面的 name
属性的所有标记值都会是 false
:
let user = {};
Object.defineProperty(user, "name", {
value: "John"
});
let descriptor = Object.getOwnPropertyDescriptor(user, 'name');
alert( JSON.stringify(descriptor, null, 2 ) );
/*
{
"value": "John",
"writable": false,
"enumerable": false,
"configurable": false
}
*/
与上面以“通常方式”创建的属性(user.name
)相比,这里的不同之处在于:当前所有的标记值都为 false
。因此,如果需要指定某个标记为 true
,必须在 descriptor
中明确指明。
现在我们再来细看,这里的每个标记具体会影响属性哪些方面的表现。
不可写的
我们通过设置 user.name
属性的 writable
标记为 false
,将该属性变成不可写的(non-writable,即不能赋值)。
现在 user
的 name
属性是修改不了了,除非再使用 defineProperty
方法去覆盖 writable
标志为 true
。
💡 提示:严格模式下会报错**
虽然赋值失败了,但上面的代码并没有报错。同样的这一段代码,如果是在严格模式下执行的,就能看见报错。
下面几乎一样的代码,不过这次变为添加新属性了。
let user = { };
Object.defineProperty(user, 'name', {
value: 'John',
// 对新属性,我们需要明确指定哪个标记为 true,
// 这里没有指定 writable 标记,因此这个属性是不可写的
enumerable: true,
configurable: true
});
console.log(user.name); // "John"
user.name = 'Pete';
console.log(user.name); // "John"。因为 name 属性不可写,所以它的值还是 "John",而非 "Pete"
不可枚举
现在给 user
添加一个 toString
方法属性。
通常情况下,对象内置的 toString
方法是不可枚举的,不会出现在 for...in
循环里。但是如果是自己添加的 toString
,就会出现在 for...in
里:
let user = {
name: 'John',
toString () {
return this.name;
}
};
// 默认,toString 出现在枚举列表中
for (let key in user) { alert(key); } // name, toString
如果我们不喜欢,就设置 enumerable: false
,toString
就不会出现在 for..in
里了:
let user = {
name: 'John',
toString () {
return this.name;
}
};
Object.defineProperty(user, 'toString', {
enumerable: false
});
// 现在 toString 不会出现在循环里了
for (let key in user) { alert(key); } // name
Object.keys
方法也会排除不可枚举属性:
alert( Object.keys(user) ); // name
不可配置
有些内置对象或属性就是不可配置的(configurable: false
)。
不可配置属性不能被删除。
比如,内置属性 Math.PI
不仅是不可写的、不可枚举的,而且还不可配置。
let descriptor = Object.getOwnPropertyDescriptor(Math, 'PI');
alert( JSON.stringify(descriptor, null, 2) );
/*
{
"value": 3.141592653589793,
"writable": false,
"enumerable": false,
"configurable": false
}
*/
所以,我们不能修改 Math.PI
的值,使用 delete Math.PI
试图删除 PI 也是无效的。
一旦将某个属性设置为不可配置后,就不能在使用 Object.defineProperty
方法修改这个属性了。
更加准确地表述是,不可配置性对 Object.defineProperty
施加了如下几个限制:
- 不可修改
configurable
标记。 - 不可修改
enumerable
标记。 - 不可将
writable: false
设置为true
(反之亦然)。 - 不可修改访问器属性的
get
/set
(但可以添加)。
下面我们将 user.name
定义成一个“永远封存”的常量了。
let user = {};
Object.defineProperty(user, 'name', {
value: 'John',
writable: false,
configurable: false
});
// 我们不能修改 user.name 的值或它的标记了
// 下面的操作都是无效的:
// user.name = 'Pete'
// delete user.name
// defineProperty(user, 'name', ...)
Object.defineProperty(user, 'name', { writable: true }); // 无效
💡 提示:“不可配置”不是“不可写”的意思
需要注意的是:一个不可配置但可写的属性,依然是可以被修改的。
configurable: false
阻止的是属性被删除或属性标记被修改,并不影响属性可不可以被修改。
Object.defineProperties
看名字大家可能就能猜到与 Object.defineProperty
的关系了。Object.defineProperty
一次只能定义一个属性,而 Object.defineProperties(obj, descriptors)
允许一次同时定义多个属性。
语法如下:
Object.defineProperties(obj, {
prop1: descriptor1,
prop2: descriptor2,
// ...
});
这里 prop1
、prop2
是要定义的属性,descriptor1
、descriptor2
则是对应属性的描述符对象。
举个例子:
Object.defineProperties(obj, {
name: { value: 'John', writable: false },
surname: { value: 'Smith', writable: false },
// ...
});
如此,我们即能一次设置多个属性了。
Object.getOwnPropertyDescriptors
与上面同理,此方法与 Object.getOwnPropertyDescriptor
的不同之处在于,[Object.getOwnPropertyDescriptors](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptors)
方法用来一次性同时获得所有属性的描述符。
与 Object.defineProperties
一起使用,可以用来“克隆”对象。区别于普通的克隆方式,这可是“标志级别”的克隆方式:
let clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj));
通常克隆对象的方式如下:
for (let key in user) {
clone[key] = user[key]
}
但它并没有复制属性标志。如果我们想要“更好”的克隆,最好使用 所以使用 Object.defineProperties
的方式。
另外一个使用 for...in
循环的缺点是会忽略 Symbol
属性,但 Object.getOwnPropertyDescriptors
方法则会返回包含 Symbol
属性在内的 所有 属性描述符。
对象级别的访问限制
属性描述符是作用在单个属性上的。
下面列出了 JavaScript 提供的、作用于整个对象级别的访问限制方法。
[Object.preventExtensions(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions)
:阻止向对象添加属性。Object.seal(obj)
:阻止向对象添加属性,阻止删除属性。此方法会将对象的所有属性设置成不可配置的(configurable: false
)。Object.freeze(obj)
:阻止向对象添加属性,阻止删除属性,阻止修改属性值。此方法会将对象的所有属性设置成不可配置的(configurable: false
)和不可写的(writable: false
)。
同时,还提供了针对这些方法的测试方法:
Object.isExtensible(obj)
:如果被阻止添加属性就返回false
(说明对象不可扩展);否则返回true
(说明对象是可扩展的)。Object.isSealed(obj)
:如果被阻止向对象添加属性和删除属性,并且对象当前的所有属性都是configuable: false
的,就返回true
(说明对象是封闭了的);否则返回false
。Object.isFrozen(obj)
:如果被阻止向对象添加属性、删除属性和修改属性值,并且对象当前的所有属性都是configurable: false, writable: false
的,就返回true
(说明对象被冻结了);否则返回false
。
但这些方法实际很少使用,了解一下便可。
(完)
📄 文档信息
**
🕘 更新时间:2020/01/09
🔗 原文链接:http://javascript.info/property-descriptors