出处:掘金

原作者:断竿散人


属性描述符:对象控制的基石

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