对象属性配置
属性标志和属性描述符
属性标志
对象的属性除了 value 之外,还有三个其余特性:
writable:为true则表示可以被修改,否则为只读;enumerable:为true表示可被循环列出,否则不可被迭代循环展示;configurable:为true表示当前属性可被修改或者删除,否则禁止被操作;将
configurable置为false之前,建议先把writable置为false,避免误修改。
-
查询对象属性的完整属性
obj:需要从中获取信息的对象。
propertyName:属性的名称。let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);示例
let user = { name: "John" }; let descriptor = Object.getOwnPropertyDescriptor(user, 'name'); /* 完整的属性描述符: { "value": "John", "writable": true, "enumerable": true, "configurable": true } */ console.info( JSON.stringify(descriptor, null, 2 ) ); -
修改属性标志
obj:要应用描述符的对象。
propertyName:要应用描述符的对象属性。若属性不存在则自动创建,否则进行修改。如果是新属性,且属性描述符中未指明的属性则默认为false。
descriptor:要应用的属性描述符对象。
Object.defineProperty(obj, propertyName, descriptor)-
只读 示例
let user = { name: "John" }; Object.defineProperty(user, "name", { writable: false }); // 仅在严格模式下,非严格模式会自动忽略 user.name = "Pete"; // Error: Cannot assign to read only property 'name' -
不可枚举 示例
配置不可枚举后,
Object.keys也会将其排除展示。let user = { name: "John", toString() { return this.name; } }; // 默认情况下,我们的两个属性都会被列出: for (let key in user) console.info(key); // name, toString Object.defineProperty(user, "toString", { enumerable: false }); // 现在我们的 toString 消失了: for (let key in user) console.info(key); // name -
不可配置 示例
不可配置的属性不能被删除,它的特性(attribute)不能被修改。
这条配置为单行道,一旦设置,无法再次修改。
一般作为内建属性使用。
但不允许配置的属性,一般是只读的,因此需要提前将writable置为false。let user = { name: "John" }; Object.defineProperty(user, "name", { configurable: false }); // 依旧可以正常修改 user.name = "Pete"; // 正常工作 delete user.name; // Error
-
-
批量修改属性描述符
语法:Object.defineProperties(user, { name: { value: "John", writable: false }, surname: { value: "Smith", writable: false }, // ... }); -
一次性获取所有属性描述符
Object.getOwnPropertyDescriptors(obj)因此我们可以实现对一个对象的完整拷贝
(包括symbol 类型的和不可枚举的属性在内的 所有 属性描述符)let clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj));
全局密封对象
1. 禁止扩展(仅防新增)
Object.preventExtensions(obj); // 设置
Object.isExtensible(obj); // 测试
2. 密封(禁止新增/删除,全部 configurable→false)
Object.seal(obj); // 设置
Object.isSealed(obj); // 测试
3. 冻结(密封 + 全部 writable→false)
Object.freeze(obj); // 设置
Object.isFrozen(obj); // 测试
4. 查看属性描述符
let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);
console.info(JSON.stringify(descriptor, null, 2));
示例
let user = { name: "John" };
// 冻结对象(最严格)
Object.freeze(user);
// 查看描述符
console.info(Object.getOwnPropertyDescriptor(user, 'name'));
// {
// "value": "John",
// "writable": false,
// "enumerable": true,
// "configurable": false
// }
// 测试状态
console.info(Object.isFrozen(user)); // true
console.info(Object.isSealed(user)); // true
console.info(Object.isExtensible(user)); // false
访问器属性
对象属性存在两种类型:
- 数据属性:之前的所有属性皆为数据属性
- 访问器属性:本质上是用于获取和设置值的函数,但外部的表现即为常规属性
Getter 与 Setter
常规使用语法:
get与set并不是需要同时出现,当只允许读取时,可以只设置get,当允许设置,但无需读取时,可以仅设置set。
// 定义
let obj = {
get propName() {
// 当读取 obj.propName 时,getter 起作用
},
set propName(value) {
// 当执行 obj.propName = value 操作时,setter 起作用
}
};
可以像常规属性一样进行使用
let user = {
name: "John",
surname: "Smith",
get fullName() {
return `${this.name} ${this.surname}`;
}
};
// 但不能对fullname进行赋值操作,因为没有set
console.info(user.fullName); // John Smith
访问器描述符
- get —— 一个没有参数的函数,在读取属性时工作,
- set —— 带有一个参数的函数,当属性被设置时调用,
- enumerable —— 与数据属性的相同,
- configurable —— 与数据属性的相同。
使用 defineProperty 创建一个 fullName 访问器:
let user = {
name: "John",
surname: "Smith"
};
Object.defineProperty(user, 'fullName', {
get() {
return `${this.name} ${this.surname}`;
},
set(value) {
[this.name, this.surname] = value.split(" ");
}
});
console.info(user.fullName); // John Smith
// 未指明的情况下,默认设置false。不进行迭代循环
for(let key in user) console.info(key); // name, surname
注意:一个属性只能是数据属性或者访问器属性,不可能同时具有。
更灵活的getter/setter
利用社区规范,采用内部属性。
从技术上讲,外部代码可以使用 user._name 直接访问 name。
但是,这儿有一个众所周知的约定,即以下划线 "_" 开头的属性是内部属性,
不应该从对象外部进行访问。
let user = {
get name() {
return this._name;
},
set name(value) {
if (value.length < 4) {
console.info("Name is too short, need at least 4 characters");
return;
}
this._name = value;
}
};
user.name = "Pete";
console.info(user.name); // Pete
user.name = ""; // Name 太短了……