事件监听器未清理是内存泄漏的直接原因,需用chrome memory面板检测detached节点及event listeners数量增长,推荐abortcontroller配合signal自动解绑,尤其注意window/document监听器和react/vue卸载时的清理时机。

事件监听器没清理,就是内存泄漏的直接原因。不是“可能泄漏”,而是“只要没解绑,就一定在拖慢 GC 回收”。
怎么确认是不是事件监听器泄漏
打开 Chrome DevTools → Memory 面板 → 拍摄 Heap Snapshot → 在 Elements 面板里筛选 detached 节点,如果数量持续增长,基本可以锁定是事件监听器没清理。
- 反复进入/退出同一页面后,
Event Listener数量只增不减(Elements → 选中任意节点 → 右侧 Event Listeners 标签) - 用
window.addEventListener('resize', () => {})绑定后没配对removeEventListener,组件卸载后监听器仍在 - 在循环中用箭头函数绑定:
els.forEach(el => el.addEventListener('click', () => {}))→ 后续根本没法 remove - 用
useCallback包裹 handler,但依赖数组漏了某个变量,导致 cleanup 里移除的是旧函数,新监听器继续挂着
AbortController 是目前最稳妥的解绑方式
它把监听器生命周期和 signal 绑定在一起,调用 controller.abort() 就自动清理所有带该 signal 的监听器,不用记 handler 引用,也不怕配对错。
文章转信息图。将文章/笔记转化为手机可读的 HTML 信息图,自动匹配视觉风格。触发场景:文章转图、笔记转图、信息图、转小红书图、做张图、可视化这篇文章、文生图。
- 只支持现代浏览器(Chrome 88+、Firefox 79+、Edge 79+;IE 完全不支持)
- 适用于动态添加/移除元素、异步加载组件、IntersectionObserver 等场景
- 示例:
const controller = new AbortController(); element.addEventListener('click', handler, { signal: controller.signal });→ 后续只需controller.abort() - 多个监听器共用一个 signal 也没问题,
abort()会一并清除
全局事件和 ref 绑定最容易漏清理
window 或 document 上的监听器,生命周期往往跨组件,是最常见的泄漏源头;而用 ref.current?.addEventListener 时,如果 current 为 null 导致报错,cleanup 函数可能根本没执行完。
- React 中必须在
useEffect返回函数里清理:return () => window.removeEventListener('scroll', handler),且handler必须是稳定引用(不能是箭头函数) - Vue 3 用
onBeforeUnmount(() => { ... }),Vue 2 用beforeDestroy,都不能只绑不拆 - 检查
ref.current是否存在再绑定:ref.current && ref.current.addEventListener('input', handler) - 避免在
useEffect里写document.addEventListener('keydown', e => {...})这类内联函数,否则 cleanup 无法匹配移除
真正难处理的不是“怎么绑”,而是“什么时候解绑”——尤其在异步加载、条件渲染、错误边界或第三方库介入时,signal 或 ref 的生命周期很容易脱钩。AbortController 提供了更健壮的抽象,但前提是你要主动构造它,而不是等框架帮你兜底。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










