TypeScript自定义LazyLoad装饰器Setter未被调用问题排查
问题分析与解决方案
你的问题核心在于装饰器的逻辑方向错了——你试图通过给属性添加setter来触发懒加载逻辑,但你的测试代码从始至终都没有对lazyProperty执行赋值操作,所以这个setter根本就没被调用过,原来的getter一直在正常执行,导致每次访问都会递增counter。
为什么你的setter从未触发?
当你给类的getter装饰时,装饰器操作的是类原型上的属性描述符。你在装饰器里给target(也就是Test的原型)添加了一个setter,但测试代码里只有test.lazyProperty的读取操作,没有任何test.lazyProperty = xxx的赋值行为,这个setter自然不会被触发,原来的getter逻辑完全没被修改,所以每次访问都会重新计算this.counter++。
正确的懒加载装饰器实现
懒加载的核心应该是直接替换原getter,加入缓存逻辑,不需要依赖setter(除非你需要支持修改缓存值)。下面是修正后的代码:
function LazyLoad(target: any, propertyKey: PropertyKey, descriptor: PropertyDescriptor) { // 保存原始的getter函数 const originalGetter = descriptor.get; // 用WeakMap缓存每个实例的计算值,避免内存泄漏 const cache = new WeakMap<any, unknown>(); console.log('LazyLoad initialized on', target.constructor.name, 'property:', propertyKey); // 替换原始getter,加入缓存逻辑 descriptor.get = function() { if (!cache.has(this)) { // 第一次访问时,调用原始getter计算值并缓存 const value = originalGetter?.call(this); cache.set(this, value); console.log('get (first time, cached)'); } else { console.log('get (returning cached value)'); } return cache.get(this); }; // 如果是只读属性,可以禁用setter,避免意外赋值破坏缓存 descriptor.set = undefined; descriptor.configurable = false; }
测试代码运行结果
使用你原来的测试代码,运行后会得到符合预期的输出:
LazyLoad initialized on Test property: lazyProperty get (first time, cached) 0 get (returning cached value) 0 1 2
关键修正点
- 直接修改原描述符:装饰器的
descriptor参数就是当前属性的描述符,直接修改它的get方法,就能覆盖原始的getter逻辑,确保实例访问属性时走我们的缓存逻辑。 - 用WeakMap做实例级缓存:每个类实例有独立的缓存值,并且当实例被垃圾回收时,缓存也会自动清理,不会造成内存泄漏。
- 不需要依赖setter:你的场景是只读的懒加载属性,所有操作都是读取,所以完全不需要通过
setter来触发缓存初始化。
关于你原代码的额外问题
即使你执行了赋值操作,原代码的setter逻辑也有问题:它会重新定义当前实例的属性为一个新的getter,但同时会把你赋值的newValue覆盖成原始getter的返回值,这显然不是你想要的行为。
内容的提问来源于stack exchange,提问作者Peter
相关产品推荐
相关产品推荐

