addEventListener 会引发内存泄漏,但仅当事件监听器未被移除且监听对象长期存活时;常见于组件销毁未清理、全局监听未解绑、闭包引用大型对象等场景。
会,但前提是出现特定条件——关键在于「监听对象长期存活」并且「事件监听器没被移除」。例如对全局 window 或生命周期较长的 DOM 节点(如 #app)重复绑定匿名函数,却没有调用 removeEventListener,监听函数及其闭包引用的变量便可能无法由 GC 回收。
常见错误场景:
addEventListener
document.addEventListener('click', () => { ... }) 做全局快捷键,但页面跳转后没解绑this),导致原本应释放的实例继续驻留传入 { once: true } 或 { passive: true } 既可改善性能,也能降低泄漏概率——这是因为 once: true 可以让浏览器自行清理监听器,不必手动调用 removeEventListener。
适用情况:
立即学习“前端免费学习笔记(深入)”;
once: true:初始化后提供点击引导,或在表单提交成功后执行关闭逻辑,这类只需响应一次的交互较为适合passive: true:适用于 scroll、touchstart 等不允许调用 preventDefault() 的场景,可避免阻塞主线程,也能间接缩短频繁触发造成的闭包驻留时间once 和 passive 无法在同一个监听器中共存(Chrome 会发出警告),需要按实际需求选择示例:
button.addEventListener('click', handler, { once: true });
需要动态绑定/解绑却不能保证监听器函数引用相同时(例如每次渲染都会创建新的箭头函数),直接使用 removeEventListener 将无法成功——原因是函数引用已经改变。此时可以使用 WeakMap 明确保存原监听器,以保证准确解绑。
操作要点:
WeakMap 作为 key 的是,value 则保存对应的监听器函数WeakMap 并逐个 removeEventListener
WeakMap 可随元素自动回收且不会阻止 GC,相比普通 Map 更安全示例:
const listenerMap = new WeakMap();<br>function bindClick(el, fn) {<br> listenerMap.set(el, fn);<br> el.addEventListener('click', fn);<br>}<br>function unbindClick(el) {<br> const fn = listenerMap.get(el);<br> if (fn) el.removeEventListener('click', fn);<br> listenerMap.delete(el);<br>}
React 的 useRef 或 Vue 的 ref 在组件完成卸载后,其 .current 旧 DOM 节点仍可能是其指向目标;此时若再次绑定事件,闭包引用与孤立监听器便会同时出现,这正是一条典型泄漏路径。
正确做法:
useEffect 提供的清理函数,并结合稳定的函数引用(如 useCallback)onBeforeUnmount 钩子内明确解绑,不要在 mounted 中使用 ref.value.addEventListener 之后便不再处理有一点最容易漏查:监听器函数捕获的对象中,是否包含本不应被长期持有的内容,比如仍在轮询的、未释放的 Canvas context,或未清理的定时器 ID AbortController)。此类泄漏并非由事件绑定本身导致,却经常与之同时发生。