HTML本身无DOM生命周期管理能力,必须手动池化节点:用Map缓存带唯一dataset.id的节点,滚出视口时设display='none'并存入池,复用时仅更新textContent/src/dataset等字段,避免removeChild导致事件、定时器、Canvas上下文永久丢失。

HTML 结构本身没有生命周期管理能力,动态组件切换时的 DOM 回收完全取决于你用什么机制控制节点增删 —— 浏览器不会自动帮你“缓存”或“复用”节点,更不会在 v-if 切换后保留旧节点的事件、定时器或 Canvas 上下文。
为什么 connectedCallback / disconnectedCallback 不能当 DOM 生命周期保险
很多开发者以为给自定义元素加了 connectedCallback 就能兜底清理,实际不是:
-
connectedCallback只在首次挂载到主文档时触发,滚动中appendChild()一个已存在的节点(比如从缓存池取回)根本不会再次调用它 -
disconnectedCallback不保证执行:用户强制关页、标签页后台冻结、进程被杀时,它直接静默跳过 - 它无法捕获手动
removeChild()后的节点状态 —— 节点一旦脱离 DOM,所有绑定的事件监听器、requestAnimationFrame、CanvasgetContext引用全丢,且不可恢复
Vue 动态组件 + keep-alive 的 DOM 行为真相
<component :is="comp"></component> 和 <keep-alive> 看似封装了生命周期,但底层仍依赖 DOM 节点是否保留在真实树中:
- 没包
<keep-alive>:每次切换都触发unmounted→ 节点被removeChild()→ DOM 销毁,事件/定时器/Canvas 全清空 - 包了
<keep-alive>:组件 VNode 被移出真实 DOM 树,但节点本身不销毁(el.parentNode变为null,但el仍存活),只是 display: none 或移入离屏容器;再激活时直接appendChild()回去,事件和上下文仍在 - 关键限制:
keep-alive只对 Vue 组件有效,对纯 HTML 结构(如<div data-id="123">)无任何作用 —— 它不接管原生节点的生命周期
大屏场景下必须手动池化 DOM 节点
高频更新(如每秒 10 帧滚动刷新)+ 数百节点时,靠浏览器默认行为必崩:
立即学习“前端免费学习笔记(深入)”;
- 错误做法:
el.remove(); newEl = createElement(); container.appendChild(newEl)→ 每帧都触发回流 + 重绑事件 + 内存抖动 - 正确做法:用
Map缓存带唯一dataset.id的节点,只改textContent、src、dataset等可变字段,绝不增删子节点 - 必须防护:初始化时就写死结构(比如固定 class 名、子元素 class 名),避免复用时因 DOM 结构错位导致数据渲染混乱
- 回收时机不是“节点消失”,而是“确认不再需要” —— 滚出视口时设
style.display = 'none'并存入池,而非调用remove()
真正容易被忽略的是:DOM 节点一旦被 removeChild(),它的全部运行时状态就永久丢失了。所谓“生命周期管理”,本质是你自己决定什么时候保留节点、什么时候擦写内容、什么时候彻底释放 —— 浏览器只提供基础 API,不提供策略。



















