Firefox 58中RemoveEventListener失效但Chrome正常,求技术排查
嘿,我立刻就发现问题所在了——你的UI_EventBus事件绑定/移除逻辑存在一个关键错误,这就是为什么在Firefox里removeEventListener失效的原因(Chrome可能做了非标准兼容,但这不是规范内的行为)。
核心问题:匿名函数引用不匹配
浏览器的标准removeEventListener API要求必须传入和添加监听时完全相同的函数引用,才能成功移除对应的事件监听。但你的代码里,每次调用UI_EventBus.addEventListener或UI_EventBus.removeEventListener时,都会创建一个全新的匿名函数:
比如移除时的这段代码:
element.removeEventListener(argEventType, function(event){ argEventHandler(event,obj); });
这里的function(event){...}是一个全新的函数实例,和你调用addEventListener时生成的那个匿名函数虽然逻辑一致,但却是完全不同的引用。浏览器找不到匹配的监听函数,自然就无法完成移除操作。
解决方案:缓存包装后的函数引用
要解决这个问题,我们需要把添加事件时生成的包装函数保存起来,这样移除时就能拿到完全相同的引用。我们可以给UI_EventBus加一个缓存结构,用来记录每个元素、事件类型对应的包装函数:
第一步:添加缓存存储
先给UI_EventBus新增一个弱映射缓存,用来存储元素对应的事件处理函数(用WeakMap可以避免内存泄漏):
UI_EventBus._eventCache = new WeakMap();
第二步:修改addEventListener方法
修改绑定逻辑,把包装后的函数存入缓存,确保每次绑定同一个原函数时,不会重复创建新的包装函数:
UI_EventBus.addEventListener = function(obj, argEventType, argEventHandler) { // 确定目标元素 var element = obj.toString().indexOf("HTML") > -1 ? obj : obj.getElement(); // 初始化缓存层级 if (!UI_EventBus._eventCache.has(element)) { UI_EventBus._eventCache.set(element, new Map()); } var elementCache = UI_EventBus._eventCache.get(element); if (!elementCache.has(argEventType)) { elementCache.set(argEventType, new Map()); } var eventCache = elementCache.get(argEventType); // 如果已经存在该处理函数的包装,先移除旧的(避免重复绑定) if (eventCache.has(argEventHandler)) { element.removeEventListener(argEventType, eventCache.get(argEventHandler)); } // 创建包装函数并存入缓存 var wrappedHandler = function(event) { argEventHandler(event, obj); }; eventCache.set(argEventHandler, wrappedHandler); // 绑定事件 element.addEventListener(argEventType, wrappedHandler); };
第三步:修改removeEventListener方法
修改移除逻辑,从缓存中取出对应的包装函数引用,再调用标准API移除:
UI_EventBus.removeEventListener = function(obj, argEventType, argEventHandler) { // 确定目标元素 var element = obj.toString().indexOf("HTML") > -1 ? obj : obj.getElement(); // 如果缓存中没有对应记录,直接返回 if (!UI_EventBus._eventCache.has(element)) return; var elementCache = UI_EventBus._eventCache.get(element); if (!elementCache.has(argEventType)) return; var eventCache = elementCache.get(argEventType); if (!eventCache.has(argEventHandler)) return; // 取出缓存的包装函数并移除监听 var wrappedHandler = eventCache.get(argEventHandler); element.removeEventListener(argEventType, wrappedHandler); // 清理缓存,避免内存泄漏 eventCache.delete(argEventHandler); if (eventCache.size === 0) { elementCache.delete(argEventType); if (elementCache.size === 0) { UI_EventBus._eventCache.delete(element); } } };
额外小提示
你的dragMouseDown方法里有一行多余的代码:
UI_EventBus.removeEventListener(obj,"mousedown", obj.dragMouseDown,[]);
这里的第三个参数[]是多余的,因为你的UI_EventBus.removeEventListener方法并没有定义这个参数,建议去掉它,避免潜在的问题。
这样修改后,不管是Firefox还是Chrome,事件移除逻辑都能正常工作了。
内容的提问来源于stack exchange,提问作者Anne Helene Holmen

