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

如何在类装饰器中保留被装饰方法的原始this上下文?

如何在装饰器中保留类方法的this上下文

你的问题核心在于事件订阅的时机不对,以及方法调用时没有绑定到类实例。让我一步步拆解问题并给出解决方案:

为什么this会变成undefined?

在你的代码中:

  1. EventHandler是类装饰器,它在类定义阶段就执行了topic.subscribe,此时还没有任何类实例被创建。
  2. 你在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 07:31:46