JavaScript属性描述符详解

1. 什么是 Property Descriptor?

每个对象属性(包括方法)都有一个对应的属性描述符,它是一个普通对象,包含以下属性:

属性类型默认值说明
value any undefined 属性的值(如果是方法,这里存储函数本身)。
writable boolean false 是否可修改属性的值。
enumerable boolean false 是否在 for...in 或 Object.keys() 中显示。
configurable boolean false 是否可删除属性或修改描述符。
get Function undefined 属性的 getter 函数(与 value 互斥)。
set Function undefined 属性的 setter 函数(与 value 互斥)。

注意:get/set 和 value/writable 不能同时存在。

2. 如何获取和修改 Property Descriptor?

(1) 获取属性描述符

使用 Object.getOwnPropertyDescriptor()

const obj = { name: "Alice", greet() { console.log("Hello!"); } };

// 获取普通属性的描述符
const nameDesc = Object.getOwnPropertyDescriptor(obj, "name");
console.log(nameDesc);
// { value: "Alice", writable: true, enumerable: true, configurable: true }

// 获取方法的描述符
const greetDesc = Object.getOwnPropertyDescriptor(obj, "greet");
console.log(greetDesc);
// { value: [Function: greet], writable: true, enumerable: true, configurable: true }

(2) 定义或修改属性描述符

使用 Object.defineProperty()

const obj = {};

// 定义一个不可写、不可枚举的属性
Object.defineProperty(obj, "id", {
  value: 123,
  writable: false,    // 不可修改
  enumerable: false,  // 不会出现在 for...in 中
  configurable: false // 不可删除或重新配置
});

console.log(obj.id); // 123
obj.id = 456;       // 静默失败(严格模式报错)
console.log(obj.id); // 123

// 尝试删除(失败)
delete obj.id; // 静默失败
console.log(obj.id); // 123

3. 方法描述符的应用场景

(1) 防止方法被修改

class Calculator {
  add(a, b) { return a + b; }
}

// 锁定 add 方法,防止被重写
Object.defineProperty(Calculator.prototype, "add", {
  writable: false,
  configurable: false
});

const calc = new Calculator();
calc.add = () => "Hacked!"; // 静默失败(严格模式报错)
console.log(calc.add(2, 3)); // 5

(2) 控制方法的可见性

const user = {
  _password: "secret", // 内部属性
  getPassword() { return this._password; }
};

// 让 getPassword 不可枚举(不出现在 JSON.stringify 或 for...in 中)
Object.defineProperty(user, "getPassword", { enumerable: false });

console.log(Object.keys(user)); // ["_password"]
console.log(JSON.stringify(user)); // {"_password":"secret"}

(3) 实现计算属性(Getter/Setter)

const circle = {
  _radius: 5,
  get diameter() { return this._radius * 2; },
  set diameter(value) { this._radius = value / 2; }
};

// 等价于:
Object.defineProperty(circle, "diameter", {
  get: function() { return this._radius * 2; },
  set: function(value) { this._radius = value / 2; },
  enumerable: true,
  configurable: true
});

console.log(circle.diameter); // 10
circle.diameter = 20;
console.log(circle._radius); // 10

4. 方法描述符与装饰器的关系

在 TypeScript/JavaScript 的方法装饰器中,装饰器函数接收的 descriptor 参数就是方法的属性描述符。通过修改它,可以拦截或增强方法行为:

function logMethod(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value; // 获取原方法

  descriptor.value = function (...args: any[]) {
    console.log(`Calling ${propertyKey} with args:`, args);
    const result = originalMethod.apply(this, args);
    console.log(`Result:`, result);
    return result;
  };

  return descriptor; // 必须返回修改后的描述符
}

class Calculator {
  @logMethod
  add(a: number, b: number) {
    return a + b;
  }
}

const calc = new Calculator();
calc.add(2, 3);
// 输出:
// Calling add with args: [2, 3]
// Result: 5

5. 注意事项

    1. 默认描述符值

      • 直接赋值(如 obj.x = 1)创建的属性是 writable: trueenumerable: trueconfigurable: true
      • 通过 Object.defineProperty() 定义的属性默认是 false
    2. 严格模式
      在非严格模式下,修改不可写属性或删除不可配置属性会静默失败;在严格模式下会抛出错误。

    3. 不可变对象
      结合 Object.freeze() 或 Object.seal() 可以进一步限制对象修改。

 

总结

  • 属性描述符 控制属性的 valuewritableenumerableconfigurable 等行为。
  • 方法描述符 是属性描述符的一种,用于方法(函数)的元编程。
  • 装饰器 通过修改 descriptor 来增强方法(如日志、权限校验)。
  • 应用场景:防止方法被重写、控制方法可见性、实现计算属性等。

通过理解 PropertyDescriptor,你可以更灵活地控制 JavaScript 对象的属性行为!

 

posted @ 2025-08-12 21:42  吴飞ff  阅读(16)  评论(0)    收藏  举报