出处:掘金
原作者:断竿散人
属性描述符:对象控制的基石
Object.defineProperty
是 JavaScript 对象控制的核心机制
ECMAScript 规范定义:
// 属性描述符标准结构
interface PropertyDescriptor {
[[Value]]?: any;
[[Writable]]?: boolean;
[[Get]]?: Function | undefined;
[[Set]]?: Function | undefined;
[[Enumerable]]?: boolean;
[[Configurable]]?: boolean;
}
描述符类型矩阵:
类型 | 可用配置项 | 冲突规则 |
---|---|---|
数据描述符 | value, writable | 与 get/set 互斥 |
存取描述符 | get, set | 与 value/writable 互斥 |
共享配置 | configurable, enumerable | 两者皆可配置 |
默认值陷阱:
const obj = {};
// 未指定配置项时默认值
Object.defineProperty(obj, "defaultProp", {});
const descriptor = Object.getOwnPropertyDescriptor(obj, "defaultProp");
console.log(descriptor);
/*
{
value: undefined,
writable: false, // 默认不可写!
enumerable: false, // 默认不可枚举!
configurable: false // 默认不可配置!
}
*/
value:属性值存储
const obj = {};
Object.defineProperty(obj, "timestamp", {
value: Date.now() // 定义时固化值
});
// 验证:值不会随时间改变
console.log(obj.timestamp === Date.now()); // false
writable:写保护机制
const systemConfig = {};
Object.defineProperty(systemConfig, "apiEndpoint", {
value: "https://api.example.com",
writable: false
});
// 严格模式下的保护
"use strict";
systemConfig.apiEndpoint = "malicious-url";
// TypeError: Cannot assign to read only property
enumerable:枚举控制
const person = {
name: "Alice",
[Symbol("id")]: "X-123"
};
Object.defineProperty(person, "age", {
value: 30,
enumerable: false
});
// 对比枚举效果
console.log(Object.keys(person)); // ["name"]
console.log(Reflect.ownKeys(person)); // ["name", "age", Symbol(id)]
configurable:配置锁
const obj = {};
// 阶段1:创建可配置属性
Object.defineProperty(obj, "phase1", {
value: 1,
configurable: true
});
// 阶段2:修改属性类型(数据→存取)
Object.defineProperty(obj, "phase1", {
get() { return this._value; },
set(v) { this._value = v; }
});
// 阶段3:禁止后续配置
Object.defineProperty(obj, "phase1", {
configurable: false
});
// 尝试再次修改将报错
Object.defineProperty(obj, "phase1", {
enumerable: true // TypeError
});
getter:访问拦截器
const temperature = {
_celsius: 0,
get fahrenheit() {
return this._celsius * 9/5 + 32;
}
};
Object.defineProperty(temperature, "celsius", {
get() { return this._celsius; },
set(value) {
if (value < -273.15) throw new Error("绝对零度不可达");
this._celsius = value;
}
});
temperature.celsius = 25;
console.log(temperature.fahrenheit); // 77
setter:赋值守卫
const account = {
_balance: 0
};
Object.defineProperty(account, "balance", {
set(value) {
if (value < 0) throw new Error("余额不可为负");
if (!Number.isInteger(value)) throw new Error("必须为整数");
this._balance = value;
},
get() { return this._balance; }
});
account.balance = 100; // 成功
account.balance = -50; // Error: 余额不可为负
对象状态控制三阶法
对象冻结等级:
普通对象 -> 不可扩展对象 -> 密封对象 -> 冻结对象
Object.preventExtensions()
不可扩展对象:不可以添加属性
const obj = { prop: "value" };
Object.preventExtensions(obj);
// 测试效果
console.log(Object.isExtensible(obj)); // false
obj.newProp = 123; // 静默失败(严格模式报错)
Object.seal()
密封对象:不可扩展对象 + 配置锁(不可配置、不可删除属性值)
const obj = { prop: "value" };
Object.seal(obj);
// 等价操作
Object.preventExtensions(obj);
Object.keys(obj).forEach(key => {
Object.defineProperty(obj, key, {
configurable: false
});
});
// 效果验证
delete obj.prop; // false(严格模式报错)
obj.prop = "new value"; // 允许修改
Object.freeze()
冻结对象:密封对象 + 属性值不可写
const obj = { prop: "value" };
Object.freeze(obj);
// 等价操作
Object.seal(obj);
Object.keys(obj).forEach(key => {
const desc = Object.getOwnPropertyDescriptor(obj, key);
if (desc.writable) {
Object.defineProperty(obj, key, { writable: false });
}
});
// 效果验证
obj.prop = "new value"; // 禁止修改
深度冻结实现:
function deepFreeze(obj) {
Object.freeze(obj);
Object.getOwnPropertyNames(obj).forEach(prop => {
const value = obj[prop];
if (value && typeof value === "object" && !Object.isFrozen(value)) {
deepFreeze(value);
}
});
return obj;
}
const config = {
db: { url: "mongodb://localhost", timeout: 3000 }
};
deepFreeze(config);
config.db.timeout = 5000; // 禁止修改(严格模式报错)
Object 静态方法全景解析
属性定义相关
// 批量定义属性
Object.defineProperties(obj, {
key1: { value: 1, writable: true },
key2: { get() { return this.key1 * 2; } }
});
// 获取属性描述符
const descriptor = Object.getOwnPropertyDescriptor(obj, "key1");
// 获取所有属性描述符
const descriptors = Object.getOwnPropertyDescriptors(obj);
对象状态检测
const sealedObj = Object.seal({});
console.log(Object.isExtensible(sealedObj)); // false
console.log(Object.isSealed(sealedObj)); // true
console.log(Object.isFrozen(sealedObj)); // false
原型链操作
const parent = { parentMethod() {} };
const child = {};
// 设置原型
Object.setPrototypeOf(child, parent);
// 获取原型
console.log(Object.getPrototypeOf(child) === parent); // true
// 创建指定原型的对象
const newObj = Object.create(parent, {
ownProp: { value: "自有属性" }
});
属性遍历方法对比
方法 | 包含不可枚举 | 包含 Symbol | 包含继承属性 |
---|---|---|---|
Object.keys() | ❌ | ❌ | ❌ |
Object.getOwnPropertyNames() | ✅ | ❌ | ❌ |
Object.getOwnPropertySymbols() | ❌ | ✅ | ❌ |
Reflect.ownKeys() | ✅ | ✅ | ❌ |
for…in | ❌ | ❌ | ✅ |