大屏卡片滚动必须用节点池复用而非频繁创建销毁,因removeChild+createElement会触发完整渲染流程并导致内存泄漏;需显式重置样式、属性,固定DOM结构,用IntersectionObserver回收,避免Canvas与DOM交互错位。

大屏页面里每秒滚动更新几十个卡片,靠 removeChild + createElement 必然掉帧——浏览器不是“删完就轻了”,而是每次销毁都丢事件、定时器、Canvas 上下文,新建又重走 layout → paint 全流程。必须用节点池复用,且不能只靠 Map 存着就完事。
为什么 connectedCallback/disconnectedCallback 在大屏里形同虚设
滚动时 appendChild 一个已挂载过的节点,connectedCallback 根本不触发;页面切后台或刷新时,disconnectedCallback 常常压根不执行。你写的清理逻辑在 80% 场景下根本没机会跑。
- 真实限制:Chrome 后台标签页冻结时,
disconnectedCallback被跳过;Firefox 移动端内存压力大时也常失效 - 后果:节点上绑定的
setInterval、addEventListener、CanvasgetContext引用全滞留,内存泄漏肉眼可见 - 别信“我加了 cleanup 逻辑”,得看 DevTools → Memory → Heap snapshot 里有没有残留闭包
复用池必须带结构锁定和字段擦除
复用不是把节点塞进 Map 就完事,关键在“擦得干净”和“结构不变”。随便 innerHTML = '' 会清掉子元素但留着内联样式;只清 textContent 又漏掉 dataset 和 class。
- 必须显式重置:
node.style.cssText = ''、node.className = ''、node.dataset.id = ''、node.removeAttribute('style') - 禁止在复用时增删子节点——所有卡片模板必须初始化就写死结构,比如固定含
.value、.status、.timestamp三个子元素 - 用
dataset.id当唯一键,不用数组索引,避免数据错位(滚动快时索引漂移很常见)
滚动中回收节点的时机和姿势
不能等节点滚出视口才回收,更不能每帧都调用 getBoundingClientRect() 判断——这个 API 会强制同步 layout,一卡就是 5ms+。
- 改用 IntersectionObserver 监听
isIntersecting === false,它异步回调且不触发 layout - 回收动作只做两件事:
node.style.display = 'none'(不 detach)、nodePool.set(node.dataset.id, node) - 绝对不要在回收时调用
node.remove()或node.parentElement.removeChild(node),那等于放弃复用
Canvas overlay 和 DOM 复用的冲突点
当 Canvas 上叠加 tooltip、告警弹窗这类 DOM 元素时,高频复用容易导致坐标错位或事件穿透失效——因为 Canvas 的 width/height 和 CSS 尺寸不一致,或 pointer-events 没配平。
- Canvas 物理尺寸必须按
canvas.width = canvas.clientWidth * window.devicePixelRatio设置,再ctx.scale(dpr, dpr) - DOM overlay 插入前,父容器临时设
style.display = 'none',插入后再恢复,屏蔽中间态 layout - Canvas 必须加
style="pointer-events: none",交互需要时再手动映射坐标,别指望 z-index 解决
真正难的不是写个 pool,而是让每个节点从出生起就带唯一 dataset.id、结构不可变、所有可变字段都靠批量赋值更新——漏掉任意一环,复用就变成内存泄漏加速器。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











