众所周知,对象可以存储属性。
到目前为止,对我们来说,属性只是一对简单的“键值”对。但实际上,对象属性是一件更灵活、更强大的事情。
在本章中,我们将学习其他配置选项,在下一章中,我们将看到如何将它们无形地转换为 getter/setter 函数。
属性标志
对象属性除了value
之外,还有三个特殊属性(所谓的“标志”)
writable
– 如果为true
,则可以更改值,否则为只读。enumerable
– 如果为true
,则在循环中列出,否则不列出。configurable
– 如果为true
,则可以删除属性并修改这些属性,否则不能。
我们还没有看到它们,因为通常它们不会显示。当我们“以通常的方式”创建属性时,所有这些属性都是 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
会更新其标记。否则,它会使用给定的值和标记创建属性;在这种情况下,如果未提供标记,则假定为 false
。
例如,此处创建了一个具有所有假标记的属性 name
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
中将它们设置为 true
。
现在让我们通过示例了解标记的效果。
不可写
让我们通过更改 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: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
的值或覆盖它。
Math.PI = 3; // Error, because it has writable: false
// delete Math.PI won't work either
我们也不能将 Math.PI
再次更改为 writable
// Error, because of configurable: false
Object.defineProperty(Math, "PI", { writable: true });
我们绝对无法使用 Math.PI
做任何事。
将属性设为不可配置是单行道。我们无法使用 defineProperty
将其改回。
请注意: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
成为“永久密封”的常量,就像内置的 Math.PI
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" });
关于更改标志有一个小例外。
我们可以将不可配置属性的 writable: true
更改为 false
,从而阻止其值修改(以增加另一层保护)。但不能反过来。
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)
- 如果禁止添加/删除属性,并且所有现有属性都有
configurable: false
,则返回true
。 - Object.isFrozen(obj)
- 如果禁止添加/删除/更改属性,并且所有当前属性均为
configurable: false, writable: false
,则返回true
。
这些方法在实践中很少使用。
评论
<code>
标签,对于多行 - 将它们包装在<pre>
标签中,对于 10 行以上 - 使用沙盒(plnkr,jsbin,codepen…)