如何在类装饰器中保留被装饰方法的原始this上下文?
如何在装饰器中保留类方法的
this上下文 你的问题核心在于事件订阅的时机不对,以及方法调用时没有绑定到类实例。让我一步步拆解问题并给出解决方案:
为什么this会变成undefined?
在你的代码中:
EventHandler是类装饰器,它在类定义阶段就执行了topic.subscribe,此时还没有任何类实例被创建。- 你在
Subscribe里直接把原型上的方法(originalMethod)存为callback,当事件触发时调用subscription.callback(event),这相当于把方法作为普通函数调用——在严格模式下,这种调用方式的this就是undefined,自然无法访问类实例的service属性。
解决方案:将订阅逻辑绑定到类实例
我们需要修改装饰器的实现,让事件订阅在类实例创建时完成,确保调用方法时this指向当前实例。
1. 修改Subscribe装饰器:保存方法名而非直接保存方法
我们只需要记录要调用的方法名称,后续在实例上通过方法名调用,确保上下文正确:
export function Subscribe(targetClass: StaticDomainEvent<any>): MethodDecorator { return function (target: any, methodName: string, descriptor: TypedPropertyDescriptor<any>) { let subscriptions = Reflect.getMetadata('subscriptions', target); if (!subscriptions) { Reflect.defineMetadata('subscriptions', subscriptions = [], target); } // 只保存方法名和事件类型,不直接保存方法引用 subscriptions.push({ methodName, targetClass }); }; }
2. 修改EventHandler装饰器:在实例构造时订阅事件
我们需要重写类的构造函数,让每个实例在创建时完成事件订阅,并且调用实例的方法:
export function EventHandler(topicKey: any): ClassDecorator { return function (target: any) { // 保存原始构造函数 const originalConstructor = target; // 创建新的构造函数,用于拦截实例创建过程 const newConstructor: any = function (...args: any[]) { // 创建类实例 const instance = new originalConstructor(...args); // 获取原型上的订阅配置 const subscriptions = Reflect.getMetadata('subscriptions', originalConstructor.prototype); const topic = Container.get<DomainTopicInterface>(topicKey); if (subscriptions?.length) { subscriptions.forEach((subscription: any) => { // 订阅事件,回调中调用实例的方法,确保this指向实例 topic.subscribe((event) => { // 可以根据targetClass过滤事件(匹配你原来的业务逻辑) if (event instanceof subscription.targetClass) { instance[subscription.methodName](event); } }); }); } return instance; }; // 保持原型链完整,避免破坏类的继承关系 newConstructor.prototype = originalConstructor.prototype; // 返回修改后的构造函数 return newConstructor; }; }
3. 验证修改后的用法
你的示例代码不需要改动,现在jobCreated方法中的this会正确指向JobHandler实例:
@EventHandler(Infra.DOMAIN_TOPIC) export class JobHandler { constructor(@Inject() private service: JobService) {} @Subscribe(JobCreated) jobCreated(event: JobCreated) { console.log(this); // 现在会输出JobHandler实例 console.log(this.service); // 可以正常访问service了 } }
额外说明
- 这种方式确保每个实例都有自己的事件订阅,符合面向对象的实例化逻辑。
- 如果需要避免重复订阅(比如单例场景),可以在构造函数中添加订阅标记,防止同一实例重复订阅。
内容的提问来源于stack exchange,提问作者Tarlen
相关产品推荐
相关产品推荐

