DOM节点复用比创建销毁更省内存,需设唯一dataset.id、结构固定、用display隐藏并Map缓存,只改textContent等字段,批量插入用DocumentFragment,避免实时集合和未清理的事件监听器。

DOM节点复用比创建销毁更省内存
频繁 createElement + removeChild 会导致内存抖动和 GC 延迟,尤其在每秒更新数百个卡片的大屏场景下,低端设备 layout 时间可能飙升 3–5 倍。浏览器不会自动池化节点,disconnectedCallback 也不可靠——后台标签页、快速滚动或崩溃时根本不会触发。
- 每个 DOM 节点至少占 1–2 KB(含样式、布局、事件缓存),深层嵌套还会放大事件监听器和计算样式缓存的间接开销
- 必须为复用节点设唯一
dataset.id,且结构固定(子元素 class 名不能动态增删) - 隐藏不用
remove(),改用el.style.display = 'none',再存入Map缓存;避免用数组——索引漂移易错位 - 只改
textContent、src、dataset等可变字段,禁用innerHTML和appendChild触发重建
批量插入必须用 DocumentFragment
直接在循环里反复调用 appendChild() 会触发多次重排(reflow),每次都要重建 render 树并计算布局,插入数百节点时主线程明显卡顿。DocumentFragment 是离线容器,所有操作不触发渲染,直到最后一次性挂载,只引起一次 layout。
- 别在 fragment 中混用已挂载节点——会自动 detach,导致意外移除
- 若需保留事件监听器,确保绑定在外层容器,并用事件委托
- 对已有节点做移动(非新建),优先用
fragment.appendChild(el)而非el.cloneNode(),避免冗余内存分配 - IE 不支持
append(),统一用appendChild()更稳妥
避免实时集合(live HTMLCollection)持续追踪
getElementsByClassName 和 getElementsByTagName 返回的是实时集合,内部维护响应式引用。只要 DOM 变动,集合就自动更新——这带来额外内存与 CPU 开销。而 querySelectorAll 返回静态 NodeList,无后续追踪成本。
- 遍历时务必缓存
length:写for (let i = 0, len = list.length; i ,而非每次读 <code>list.length - 高频查询优先用
querySelector('#id')或带 class 的querySelector('.item[data-id="123"]') - 不要把
getElementsByClassName结果赋给全局变量长期持有——它会隐式延长 DOM 节点生命周期
懒加载 + 滚动激活比全量 display:none 更有效
display: none 不等于释放资源:元素仍在 DOM 中,仍参与样式计算(虽不绘制),首次显示时仍要完整重绘。对超长页面(如 2800+ 行)或多个大型 section,应结合滚动位置动态激活/停用。
立即学习“前端免费学习笔记(深入)”;
- 仅在用户即将滚动进入视口前(如提前 200px)才设
el.classList.add('active')(对应.active { display: block; }) - 对已滚出可视区较远(如顶部距离 > 1000px)的区块,设
el.classList.remove('active') - 配合
select控制区域切换时,不要一次性渲染全部section,而是按选中值动态激活对应节点 - 低版本 Safari 不支持
IntersectionObserver,可用window.addEventListener('scroll', ...)+getBoundingClientRect()兜底
performance.memory.usedJSHeapSize 就会持续上涨,哪怕页面看着“空”。



















