shadow dom 中的资源不会自动释放,需手动清理:自定义元素用 disconnectedcallback,非自定义元素监听 remove 事件;定时器、观察者、事件监听器等须显式清除;嵌套 shadow dom 需递归清理;异步操作应检查 isconnected。

Shadow DOM 中的资源不会自动随节点卸载而释放
Shadow DOM 本身不提供资源生命周期管理机制。即便你用 element.attachShadow({ mode: 'closed' }) 创建了 Shadow Root,其中的 <img>、addEventListener、setTimeout、IntersectionObserver 或自定义 Web Component 实例,都不会因为宿主元素被 remove() 或从 DOM 树中移除而自动清理。
手动回收的关键时机是 disconnectedCallback 或 remove 事件监听
对自定义元素而言,最可靠的清理入口是 disconnectedCallback —— 它在元素从文档中被移除时触发(包括 parentNode.removeChild(el)、el.remove()、innerHTML = '' 等场景)。但要注意:
-
disconnectedCallback不保证 Shadow Root 还存在(例如组件被快速反复挂载/卸载时,Shadow Root 可能已被销毁);建议先判空:if (this.shadowRoot) { ... } - 若组件非自定义元素(比如纯 JS 动态 attach),需显式监听宿主元素的
remove事件:hostElement.addEventListener('remove', cleanup, { once: true }) - 不要依赖
beforeunload或visibilitychange:它们与单个组件生命周期无关,且可能错过局部卸载
常见资源类型及对应回收方式
不同资源需不同处理策略,混用会导致内存泄漏:
-
<img>或<video></video>:调用el.src = ''或el.removeAttribute('src'),防止浏览器缓存解码器或网络连接 - 事件监听器:必须用相同函数引用移除,避免匿名函数;推荐用绑定方法或 WeakMap 存储 handler:
this.shadowRoot.removeEventListener('click', this._boundClickHandler) - 定时器 / 动画帧:
clearTimeout(this._timerId)、cancelAnimationFrame(this._rafId),并清空变量引用 - IntersectionObserver / ResizeObserver:
observer.disconnect()后置为null,否则 observer 会强持有目标元素 - 第三方库实例(如 Chart.js):
chart.destroy()是必须的,仅移除 canvas 元素无效
Shadow DOM 深层嵌套时回收要逐层递归
如果 Shadow Root 内部还包含其他自定义元素或动态创建的子 Shadow Root(例如通过 el.attachShadow() 嵌套),父组件的 disconnectedCallback 不会自动触发子组件的清理逻辑。
- 必须在父组件清理前,主动遍历 shadowRoot 内所有自定义元素并调用其公开的
destroy()方法(如有) - 或统一使用
composed: true的事件冒泡机制,在父级监听子组件发出的will-unmount自定义事件 - 避免在 Shadow Root 中用
document.querySelectorAll查找子组件:它无法跨 Shadow Boundary,应改用this.shadowRoot.querySelectorAll(':scope > custom-child')
真正容易被忽略的是异步操作中的闭包引用 —— 比如一个 fetch().then(...) 回调里仍访问 this.shadowRoot,即使组件已卸载,Promise 未 resolve 就会持续持有整个 Shadow Root。这类情况必须在发起请求前加标记,并在回调开头检查 if (!this.isConnected) return。











