DOM操作不慢,慢在错误读写顺序、低效查询和频繁重排;应优先用getElementById、缓存节点、批量插入用DocumentFragment、避免在循环中读写offsetTop等触发同步布局的属性。
DOM 操作本身不慢,但错误的读写顺序、低效查询和频繁触发重排会直接让 JS 卡住。关键不是“少用 DOM”,而是避开那几个高代价动作。
浏览器对 id 有内部哈希索引,document.getElementById('foo') 是接近 O(1) 的查找;而 document.querySelectorAll('#foo') 仍走完整 CSS 选择器引擎,哪怕只写一个 ID 也多一层解析开销。
getElementById 或提前缓存: const el = document.getElementById('header');
Cannot read property 'xxx' of null
循环里反复调用 appendChild 会让浏览器每插一个就尝试计算布局,100 次就是 100 次潜在重排。实测插入 1000 个 <li>,用 DocumentFragment 比逐个 appendChild 快 3–8 倍。
DocumentFragment:适合需要保留节点引用、绑定事件或复用元素的场景innerHTML 更快但有风险:会清空原有子节点、销毁已绑定事件、丢失表单输入值(比如 <input value="用户刚打的字">)innerHTML + 模板字符串,但记得手动转义内容防 XSS这些属性要求浏览器立刻返回准确布局值,会强制同步完成所有待处理样式计算和重排——哪怕你前一秒刚改了 className,它也要等渲染流水线卡住再算。在循环里交替读写,就是典型的“布局抖动”(layout thrashing)。
立即学习“前端免费学习笔记(深入)”;
for (let i = 0; i
offsetTop、getBoundingClientRect(),存进数组;再遍历一遍统一写样式requestAnimationFrame 包一层,把读操作对齐到下一帧开始时,避免打断当前渲染把 100 个按钮的 click 监听器全收在 document 上,看似省事,但每次点击都要从 event.target 一路向上查匹配,路径越长越耗 CPU。
table,列表就挂 ul,别一股脑扔给 document 或 body
e.target.classList.contains('btn-save') 比 e.target.matches('.btn-save') 快,后者要解析整个选择器字符串throttle)或防抖(debounce),否则 JS 主线程直接被占满,页面冻结真正卡顿往往不是 DOM API 调用慢,而是你无意中把它变成了重排触发器。读布局、写样式、再读布局……这个循环只要出现在高频回调里,性能就没了。