深入了解对象属性标志以及描述符
属性标志以及描述符
正如我们所知,对象可以存储属性。
到目前为止,属性对我们来说只是一个简单的“键-值”对。但对象属性实际上是一个更灵活和强大的东西。
本章我们将学习额外的配置选项,下一章我们将看到如何无形地将它们转换为getter/setter
函数。
属性标志
652/5000 对象属性除了值之外,还有三个特殊的属性(所谓的“标志”):
writable
—如果为true
,该值可以修改,否则为只读。enumerable
——如果为true
,则在循环中列出,否则不列出。configurable
-如果为true
,属性可以被删除,这些属性可以被修改,否则不。
我们还没看到他们,因为他们通常不会出现。当我们以“通常的方式”创建一个属性时,它们都是正确的。但我们也可以随时改变它们。
首先,让我们看看如何获得这些标志。
Object.getOwnPropertyDescriptor
允许查询关于属性的完整信息。
语法是:
let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);
obj
:
要从中获取信息的对象。
propertyName
:
属性的名称。
返回值是一个所谓的“属性描述符”对象:它包含值和所有标记。
例如:
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
用于应用描述符的对象及其属性。
descriptor
要应用的属性描述符对象。
如果该属性存在,defineProperty
将更新其标记。否则,它将创建具有给定值和标志的属性;在这种情况下,如果没有提供标志,则假定它为假。
例如,这里创建了一个带有所有假标志的属性名:
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
比较一下:现在所有的标志都是假的。如果这不是我们想要的那么我们最好在descriptor
中将它们设为真。
现在让我们通过例子来看看标记的效果。
不可写
让我们通过改变writable
标志使user.name
不可写(不能被重新分配):
let user = {
name: "John"
};
Object.defineProperty(user, "name", {
writable: false
});
user.name = "Pete"; // Error: Cannot assign to read only property 'name'
现在没有人可以更改我们的用户名,除非他们应用自己的defineProperty
来覆盖我们的用户名。
下面是相同的例子,但属性是从头创建的:
let user = { };
Object.defineProperty(user, "name", {
value: "John",
// for new properties we need to explicitly list what's true
enumerable: true,
configurable: true
});
alert(user.name); // John
user.name = "Pete"; // Error
不可枚举
现在让我们为user
添加一个自定义toString
。
通常,对象的内置toString
是不可枚举的,它不会出现在for..in
。但如果我们添加自己的toString
,那么默认情况下它会出现在for..in
,是这样的:
let user = {
name: "John",
toString() {
return this.name;
}
};
// By default, both our properties are listed:
for (let key in user) alert(key); // name, toString
如果我们不喜欢它,那么可以设置enumerable:false
。那么它就不会出现在for..in
循环,就像内置的一样:
let user = {
name: "John",
toString() {
return this.name;
}
};
Object.defineProperty(user, "toString", {
enumerable: false
});
// Now our toString disappears:
for (let key in user) alert(key); // name
不可枚举属性也被排除在Object.keys
之外:
alert(Object.keys(user)); // name
不可配置
不可配置的标志(configurable:fals)有时是内置对象和属性的预置标志。
不能删除不可配置的属性。
例如,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
的值。或者重写它。
Math.PI = 3; // Error
// delete Math.PI won't work either
使属性不可配置是一条单行道。我们不能用defineProperty
把它改回来。
确切地说,不可配置性在defineProperty
上强加了几个限制:
无法更改可配置标志。
不能改变enumerable标志。
不能将
writable: false
改为true
(反过来也可以)。不能更改访问器属性的
get/set
(但如果没有,可以分配它们)。
“configurable:false”的思想是为了防止属性标记的更改和删除,同时允许更改其值。
这里user.name
是不可配置的,但是我们仍然可以修改它(因为它是可写的):
let user = {
name: "John"
};
Object.defineProperty(user, "name", {
configurable: false
});
user.name = "Pete"; // works fine
delete user.name; // Error
这里我们将user.name
设为“永久密封”常数:
let user = {
name: "John"
};
Object.defineProperty(user, "name", {
writable: false,
configurable: false
});
// won't be able to change user.name or its flags
// all this won't work:
user.name = "Pete";
delete user.name;
Object.defineProperty(user, "name", { value: "Pete" });
Object.defineProperties
有一个方法Object.defineProperties(obj, descriptors)
允许一次定义许多属性。
语法是:
Object.defineProperties(obj, {
prop1: descriptor1,
prop2: descriptor2
// ...
});
例如:
Object.defineProperties(user, {
name: { value: "John", writable: false },
surname: { value: "Smith", writable: false },
// ...
});
Object.getOwnPropertyDescriptors
要一次性获得所有属性描述符,我们可以使用Object.getOwnPropertyDescriptors(obj)
方法。
和object.defineproperties
一起,它可以作为一种“识别标志”的方式来克隆对象:
let clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj));
通常,当我们克隆一个对象时,我们使用赋值来复制属性,像这样:
for (let key in user) {
clone[key] = user[key]
}
但这不会复制标记。因此,如果我们想要一个“更好的”克隆,那么Object.defineProperties
是首选。
另一个区别是for…in
会忽略符号属性,但Object.getOwnPropertyDescriptors
返回所有属性描述符,包括符号描述符。
全局密封对象
属性描述符在单个属性的级别上工作。
还有一些方法可以限制对整个对象的访问:
Object.preventExtensions(obj)
禁止向对象添加新属性。
Object.seal(obj)
禁止添加/删除属性。设置configurable: false
为所有现有的属性。
Object.freeze(obj)
禁止添加/删除/更改属性。设置configurable: false
,writable: false
所有现有的属性。
对他们也有一些测试:
Object.isExtensible(obj)
如果禁止添加属性,则返回false,否则返回true。
Object.isSealed(obj)
如果禁止添加/删除属性,则返回true, 并且所有现有属性都是configurable: false
。
Object.isFrozen(obj)
如果禁止添加/删除/更改属性,则返回true,并且当前所有属性都是configurable: false, writable: false
。