- Published on
对象属性描述符
在 JavaScript 中,对象的属性并不仅仅是一个简单的键值对。每个属性的背后,都由一组内部的特性 (attributes) 所定义,这些特性共同构成了一个属性描述符 (Property Descriptor)。这个描述符精确地控制了该属性的行为,例如它是否可写、可枚举或可配置。理解并掌握通过 Object.defineProperty 等元编程 (Metaprogramming) API 来操作这些底层特性,是实现诸如“只读属性”、数据劫持(如 Vue 2 的响应式原理)和创建不可变对象等高级功能的关键。
属性描述符的类型与特性
属性描述符主要分为两种相互排斥的类型。
数据描述符 vs. 存取描述符
- 数据描述符 (Data Descriptor): 拥有一个具体的数据值。其特性包含
value,writable,enumerable,configurable。 - 存取描述符 (Accessor Descriptor): 由一对 getter-setter 函数来定义。其特性包含
get,set,enumerable,configurable。
一个属性描述符不能同时拥有 value 或 writable 和 get 或 set。
属性特性 (Property Attributes)
对于通过常规方式(如 const obj = { a: 1 };)创建的属性,其特性默认为 true。这些特性也被称为“属性标志”。
writable: 一个布尔值。当为true时,该属性的value可以被修改。当为false时,该属性变为只读。enumerable: 一个布尔值。当为true时,该属性会出现在对象的枚举中(例如,在for...in循环或Object.keys()的结果中)。configurable: 一个布尔值。当为true时,该属性的描述符可以被再次修改,并且该属性可以从其所属对象中被删除。当为false时,该属性将不可删除,且除了value和writable之外的所有特性都不可再被修改。
核心 API:defineProperty 与 getOwnPropertyDescriptor
Object.getOwnPropertyDescriptor()
此方法用于查询一个对象自有属性(非继承属性)的完整属性描述符。
const user = { name: "Alice" };
const descriptor = Object.getOwnPropertyDescriptor(user, 'name');
console.log(descriptor);
// 输出:
// {
// value: "Alice",
// writable: true,
// enumerable: true,
// configurable: true
// }
Object.defineProperty()
此方法用于在一个对象上定义一个新属性,或修改一个现有属性的特性。
- 修改现有属性:
defineProperty会更新指定属性的标志。 - 定义新属性: 如果属性不存在,
defineProperty会使用给定的值和标志创建一个新属性。
defineProperty 的默认行为当使用 defineProperty 定义一个新属性时,如果没有在描述符中显式提供 writable, enumerable, configurable 标志,它们的默认值将全部为 false。这与通过对象字面量创建属性的行为截然不同。
defineProperty 实践const user = {};
// 使用 defineProperty 创建一个新属性
Object.defineProperty(user, 'id', {
value: '123',
writable: false, // 设置为只读
enumerable: true,
// configurable 未提供,默认为 false
});
// 查询描述符
console.log(Object.getOwnPropertyDescriptor(user, 'id'));
// { value: '123', writable: false, enumerable: true, configurable: false }
// 尝试修改只读属性 (在非严格模式下静默失败,在严格模式下抛出 TypeError)
user.id = '456';
console.log(user.id); // "123"
// 尝试删除不可配置的属性 (静默失败或抛出 TypeError)
delete user.id;
console.log(user.id); // "123"
对象的整体限制 (Object-level Restrictions)
除了对单个属性进行精细控制外,JavaScript 还提供了一系列 API,用于对整个对象的扩展性和属性的可配置性施加限制。这些限制是不可逆的。
Object.preventExtensions(obj)- 作用: 禁止向对象添加新的属性。
- 检测:
Object.isExtensible(obj)返回false。
Object.seal(obj)- 作用: 禁止添加新属性,也禁止删除现有属性。它会将所有现有属性的
configurable标志设置为false。 - 检测:
Object.isSealed(obj)返回true。
- 作用: 禁止添加新属性,也禁止删除现有属性。它会将所有现有属性的
Object.freeze(obj)- 作用: 这是最严格的限制级别。它禁止添加、删除、修改任何属性。它会将所有现有属性的
configurable和writable标志都设置为false。 - 检测:
Object.isFrozen(obj)返回true。
- 作用: 这是最严格的限制级别。它禁止添加、删除、修改任何属性。它会将所有现有属性的