disconnectedcallback 不保证执行,仅在原生 dom 移除操作(如 remove())时大概率触发;页面刷新、tab 关闭等场景常被跳过,因此定时器、observer、事件监听等资源必须显式清理且不可依赖该钩子单点保障。

disconnectedCallback 为什么没执行
它只在元素被 remove()、replaceWith() 或父节点调用 removeChild() 时大概率触发,但页面刷新、关闭 tab、后台销毁等场景下浏览器可能直接跳过。常见误判:
-
innerHTML = ''清空父容器 → 不触发disconnectedCallback - 用 Vue/React 动态卸载组件 → 若底层未走原生 DOM 移除路径,也不会进该钩子
- 元素被移动到 iframe(触发
adoptedCallback)→ 原文档的disconnectedCallback仍会执行,但新文档里要重新挂载才触发connectedCallback
清理定时器和 Observer 的正确写法
所有持有引用的资源必须显式释放,且不能依赖 disconnectedCallback 单点保障:
- 定时器:在
disconnectedCallback中调用clearTimeout(this._timer)或clearInterval(this._interval),同时建议初始化时用this._timer = null防重复清理 -
IntersectionObserver/ResizeObserver:必须调用this._io.disconnect(),否则观察器持续持有目标节点引用 - 全局事件监听:如
window.addEventListener('resize', this._onResize),对应需window.removeEventListener('resize', this._onResize) - fetch 请求:推荐搭配
AbortController,在disconnectedCallback中调用this._abortController.abort(),即使钩子未执行也能中断请求
customElements.unregister() 不存在怎么办
截至 2026 年 6 月,Chrome/Firefox/Safari 均未实现 customElements.unregister()。所谓“注销”只是逻辑层面操作:
- 先遍历 DOM 查找所有已挂载实例:
document.querySelectorAll('my-element'),逐个调用el.remove() - 若类中用了静态缓存(如
MyElement._instances = new WeakMap()),需在disconnectedCallback中MyElement._instances.delete(this) - 避免在类外部维护全局数组存储实例——这会阻碍 GC,应优先用
WeakMap或直接靠 DOM 树关系定位
SSR 和动态注册场景下的销毁陷阱
服务端渲染或 Vite 动态导入时,“销毁”容易变成伪命题:
- SSR 输出的
<my-element data-id="42"></my-element>在客户端 hydration 后才真正成为自定义元素,此时disconnectedCallback才可能被调用 - Vite 中用
import('./MyElement.js').then(() => customElements.define(...))→ 若用户快速跳转路由,模块加载中途取消,define()可能根本没执行,也就谈不上销毁 - 第三方库封装(如 Chart.js)必须在
disconnectedCallback中调用this._chart.destroy(),否则 canvas 上下文和事件监听器全留在内存里
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











