必须配对 removeEventListener 解绑事件监听器,匿名函数无法解绑,推荐使用具名函数、WeakMap 缓存、AbortController 或生命周期钩子清理,避免内存泄漏。

不选。HTML事件本身没有“选不选”的余地,内存泄漏也不是靠切换事件类型就能规避的——问题出在你绑了没解、解了没对上、或者根本不知道谁还在引用它。
addEventListener 后必须配对 removeEventListener
只要调用过 addEventListener,且绑定的回调持有外部变量(比如组件实例、大数组、闭包里的 data),那这个 DOM 节点和相关对象就可能卡在内存里不释放。
- 匿名函数无法被
removeEventListener匹配,每次() => {}都是新函数,解绑无效 - 推荐写法:
const handler = () => { /* ... */ };,绑定和解绑都传同一个handler - 在 Vue 的
onUnmounted、React 的useEffect清理函数、或自定义元素的disconnectedCallback中执行解绑 - 如果用了
{ once: true },不用手动解绑,但注意它和{ capture: true }互斥,不能混用
document 上的事件委托最容易漏解绑
为性能把 click 统一挂到 document 上很常见,但“挂一次、用一生”就是泄漏温床——尤其 handler 里引用了某个模块的局部状态或已卸载的 DOM 节点。
文章转信息图。将文章/笔记转化为手机可读的 HTML 信息图,自动匹配视觉风格。触发场景:文章转图、笔记转图、信息图、转小红书图、做张图、可视化这篇文章、文生图。
- 不要在全局监听里直接访问
e.target.dataset.id后赋值给长期存活对象(如 class 的this.cache) - 需要缓存 target 关联数据时,改用
WeakMap,键是 DOM 节点,值自动随节点回收 - 若模块有明确生命周期(如弹窗打开/关闭),就在关闭回调里调用
document.removeEventListener('click', handler) - 避免用
bind动态生成 handler,除非你把原始函数引用存下来供解绑用
capture 阶段监听更隐蔽,但泄漏逻辑一样
{ capture: true } 不改变内存泄漏的本质,只是开发者更容易忽略它——因为事件没“落在目标上”,误以为不重要。
立即学习“前端免费学习笔记(深入)”;
- 全局捕获监听(如
document.addEventListener('keydown', handler, { capture: true }))必须和冒泡监听一样,在对应时机清理 - 不能依赖
once自动清理,once和capture不能共存 - 建议用
Map管理 document → handler 映射,销毁前批量遍历清除 - Chrome 95+ 可考虑用
AbortController:传入signal选项,调用controller.abort()自动解绑所有关联监听
真正难定位的不是“哪行代码绑错了”,而是“哪个闭包还拽着那个 DOM 节点不放”。用 Chrome DevTools 的 Memory 面板拍堆快照,筛选 detached DOM trees,再点开 Retainers,往往一眼就看到是哪个 addEventListener 的回调在卡住它。


















