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

TypeScript装饰器绑定window事件后removeEventListener无法移除问题

事件移除失效的根本原因

Function.prototype.bind() 每次调用都会生成一个全新的函数实例,你在addBindings中添加监听器时调用binding.binding.bind(this)生成了一个绑定了this的匿名函数,后续removeBindings执行时又调用了一次bind,两次返回的函数引用完全不同。而removeEventListener要求传入和添加监听器时完全相同的函数引用才能匹配到目标监听器,因此移除操作无效。

修复方案

你需要在类实例上存储已经绑定好this的函数引用,添加和移除监听器时都使用同一个存储的引用即可。
修改后的GameObject装饰器代码如下:

// 先定义EventBinding类型,你原本应该已经有这个定义
type EventBinding = {
  type: string;
  name: string[];
  binding: (...args: any[]) => void;
}

export const EVENT_BINDING_PREFIX = '__attachEventBinding_'

export function GameObject(options?: object) {
  return function <T extends { new(...args: any[]): object }>(constructor: T) {
    return class extends constructor {
      // 新增属性存储绑定后的监听器引用
      private _boundListeners: Record<string, EventListener> = {};

      constructor(...args: any[]) {
        super(...args)
        this.addBindings()
      }

      get bindings() {
        return Object.getOwnPropertyNames(Object.getPrototypeOf(Object.getPrototypeOf(this)))
          .filter(i => i.startsWith(EVENT_BINDING_PREFIX))
          .map(b => (this as any)[b] as EventBinding)
      }

      addBindings() {
        this.bindings.forEach(binding => {
          const [nodeOrEvent, event] = binding.name
          if (nodeOrEvent === 'window' && event.length > 0) {
            // 先绑定this,存到实例属性里
            const boundFn = binding.binding.bind(this) as EventListener;
            this._boundListeners[event] = boundFn;
            // 添加时用存储的绑定后函数
            window.addEventListener(event, boundFn)
          }
        })
      }

      removeBindings() {
        this.bindings.forEach(binding => {
          const [nodeOrEvent, event] = binding.name
          if (nodeOrEvent === 'window' && event) {
            // 直接取存储的绑定后函数用于移除
            const boundFn = this._boundListeners[event];
            if (boundFn) {
              window.removeEventListener(event, boundFn);
              // 移除后清理引用
              delete this._boundListeners[event];
            }
          }
        })
      }
    }
  }
}

另外注意你示例代码中方法装饰器定义的名字是Event,但使用时写的是@AddEvent,记得统一命名避免报错。

内容的提问来源于stack exchange,提问作者Get Off My Lawn

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 17:48:02