使用TypeScript装饰器实现属性Getter和Setter遇阻求助
我懂你现在卡在哪了——在装饰器里直接写this.resetCssCache根本拿不到实例上的方法,这坑我之前也踩过!核心问题在于装饰器是在类定义阶段执行的,这时候还没有创建类的实例,this指向的不是你后来new出来的Test实例,而是类本身(严格模式下甚至是undefined),所以自然找不到实例方法resetCssCache。
先拆解下你的代码问题:
export class Test {
...
@Field.watch({onSet: this.resetCssCache})
public layoutId = 0;
...
public resetCssCache() { ... }
...
}
这里的this.resetCssCache在装饰器执行时,this指向的是Test类(而非实例),而resetCssCache是实例方法,所以此时这个值是undefined,导致onSet回调根本不会触发,甚至可能直接报错。
下面给你两种可行的解决方案,推荐第一种:
方案一:让装饰器通过方法名调用实例方法(最优选择)
修改Field.watch装饰器的实现,允许传入方法名称的字符串,然后在get/set钩子中通过实例去调用对应的方法。这样能确保this始终指向当前实例,完全规避绑定问题。
首先更新装饰器代码:
export class Field { public static watch(watchers: { onGet?: string | (() => void), onSet?: string | ((newValue: any, oldValue: any) => void) }) { return function(target: any, propertyKey: string) { // 保存原始属性的初始值 let storedValue = target[propertyKey]; // 重定义属性的getter和setter Object.defineProperty(target, propertyKey, { get: function() { // 处理onGet回调 if (typeof watchers.onGet === 'string') { this[watchers.onGet](); } else if (typeof watchers.onGet === 'function') { // 如果是直接传函数,手动绑定实例this watchers.onGet.call(this); } return storedValue; }, set: function(newValue) { const oldValue = storedValue; // 处理onSet回调 if (typeof watchers.onSet === 'string') { this[watchers.onSet](newValue, oldValue); } else if (typeof watchers.onSet === 'function') { watchers.onSet.call(this, newValue, oldValue); } storedValue = newValue; }, enumerable: true, configurable: true }); }; } }
然后在Test类里使用时,传入方法名称的字符串即可:
export class Test { @Field.watch({ onSet: 'resetCssCache' }) public layoutId = 0; public resetCssCache(newValue: number, oldValue: number) { console.log(`layoutId从${oldValue}更新为${newValue}`); // 这里写你的缓存重置逻辑 } }
这种方式的好处是既保证了this指向正确,又不会额外占用内存,所有实例共享原型上的方法,是最推荐的实现方式。
方案二:用箭头函数绑定this(不推荐,有内存开销)
如果你不想修改装饰器的实现,也可以把resetCssCache定义成箭头函数。箭头函数的this会在定义时绑定到类的实例,但代价是每个实例都会创建一份独立的箭头函数,对于大量实例的场景会浪费内存。
代码示例:
export class Test { @Field.watch({ onSet: this.resetCssCache }) public layoutId = 0; // 用箭头函数定义方法,强制绑定实例this public resetCssCache = (newValue: number, oldValue: number) => { console.log(`layoutId从${oldValue}更新为${newValue}`); // 缓存重置逻辑 }; }
这种方式虽然能解决问题,但不推荐在性能敏感的场景使用。
最后要注意的配置
确保你的tsconfig.json里开启了装饰器相关的配置:
{ "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true, // 其他编译配置... } }
内容的提问来源于stack exchange,提问作者mvermand

