You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为TypeScript访问器方法创建enumerable装饰器

实现可枚举访问器的@enumerable装饰器方案

问题根源解析

你遇到的核心问题来自JavaScript属性枚举的规则和私有字段的访问限制:

  1. 原型属性与实例属性的区别:类的访问器默认定义在原型上,Object.keys()/Object.getOwnPropertyNames()仅返回实例自身的可枚举属性,原型上的属性不会被包含——这就是你用装饰器修改原型访问器的enumerable后,Object.keys仍无法获取属性a的原因。
  2. for...in的特殊性:for...in会遍历实例及其原型链上的所有可枚举属性,所以能看到原型上被标记为可枚举的a。
  3. Object.entries报错原因:当Object.entries尝试遍历原型上的a访问器时,它会在原型对象上调用getter,而原型对象没有类实例的私有字段#privateVal1,因此抛出访问错误。

可行的装饰器实现方案

要让访问器能被Object.keys/Object.entries正确枚举,需要将原型上的访问器复制到实例自身并标记为可枚举。以下是结合属性装饰器和类装饰器的实现:

// 存储类需要枚举的属性名,用WeakMap避免内存泄漏
const enumerableProps = new WeakMap<Function, string[]>();

// 属性装饰器:标记需要枚举的访问器
function enumerable(value: boolean = true) {
  return function (target: any, propertyKey: string) {
    if (!value) return;
    const constructor = target.constructor;
    const props = enumerableProps.get(constructor) || [];
    if (!props.includes(propertyKey)) {
      props.push(propertyKey);
    }
    enumerableProps.set(constructor, props);
  };
}

// 类装饰器:修改构造逻辑,实例化时复制访问器到实例
function EnumerableClass<T extends { new(...args: any[]): {} }>(constructor: T) {
  return class extends constructor {
    constructor(...args: any[]) {
      super(...args);
      const props = enumerableProps.get(constructor);
      if (props) {
        for (const prop of props) {
          const protoDescriptor = Object.getOwnPropertyDescriptor(constructor.prototype, prop);
          if (protoDescriptor) {
            // 将原型访问器复制到实例,显式设置enumerable为true
            Object.defineProperty(this, prop, {
              ...protoDescriptor,
              enumerable: true
            });
          }
        }
      }
    }
  };
}

使用示例

@EnumerableClass
class MyClass {
  #privateVal1: any;
  #privateVal2: any;

  constructor() {
    // 无需手动调用makeEnumerable
  }

  @enumerable(true)
  get a() {
    return this.#privateVal1;
  }

  set a(val: any) {
    this.#privateVal1 = val;
  }

  @enumerable(true)
  get b() {
    return this.#privateVal2;
  }

  set b(val: any) {
    this.#privateVal2 = val;
  }
}

const instance = new MyClass();
instance.a = 5;
instance.b = 6;

console.log(Object.keys(instance)); // ['a', 'b']
console.log(Object.entries(instance)); // [['a', 5], ['b', 6]]
console.log(Object.getOwnPropertyNames(instance)); // ['a', 'b']

const keys = [];
for (const key in instance) {
  keys.push(key);
}
console.log(keys); // ['a', 'b']

方案原理

  1. 属性装饰器:收集当前类中需要设置为可枚举的访问器属性名,存储在WeakMap中,避免对类构造函数造成额外内存占用。
  2. 类装饰器:继承原类并重写构造函数,在实例化时将收集到的访问器从原型复制到实例自身,并显式标记enumerable: true。此时访问器的this指向实例,能正确访问私有字段,同时Object.keys等API能识别到这些实例自身的可枚举属性。

内容的提问来源于stack exchange,提问作者Harry Mustoe-Playfair

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 01:33:25