Appearance
DOM 操作:document.write 与 innerHTML
来源:
js/dom.html
修改页面内容有两种方式,行为差异很大。
演示一:document.write() — 全页面覆盖
页面加载完成后调用 document.write(),会清除整个页面并写入新内容。
原始注释: "全页面覆盖,直接回流" —
document.write()在页面加载后调用会清空整个文档树,触发完全重排。
演示二:innerHTML — 替换整个 body
document.body.innerHTML = 1 替换的是整个 body 的内容,和 document.write() 效果类似。
演示三:element.innerHTML — 真正的局部更新
针对具体元素修改 innerHTML,只影响该元素,其他内容不受影响。
对比
document.write() | document.body.innerHTML | element.innerHTML | |
|---|---|---|---|
| 作用范围 | 整个文档 | 整个 body | 指定元素 |
| 加载后调用 | 清空页面重写 | 清空 body 重写 | 只替换目标元素内容 |
| 性能 | 触发完全重排 | 触发 body 重排 | 只重排受影响区域 |
| 适用场景 | 几乎不用 | 几乎不用 | 动态更新局部内容 |
结论:
document.write()和document.body.innerHTML都会大范围替换内容,应避免使用。针对具体元素的element.innerHTML才是正确的局部更新方式。
JS 操作 DOM 是同步还是异步?
结论:JS 操作 DOM 是同步执行的,但看起来像“异步”,原因是:
- JS 引擎线程与 GUI 渲染线程互斥:JS 执行期间 GUI 无法渲染,导致 DOM 操作的效果不会立即显示
- 浏览器渲染优化策略:将同一 DOM 的多个样式修改合并后一次性渲染,只显示最终状态
经典问题:动态创建 DOM 后立刻添加 class,transition 动画不生效
js
// 问题代码:transition 不会生效
let div = document.createElement('div')
div.classList.add('initial') // 状态 A
body.appendChild(div)
div.classList.add('final') // 状态 B,直接覆盖了 A,没有过渡解决方案:强制回流(读取 clientLeft 强制触发渲染,使初始状态生效)
js
div.classList.add('initial')
body.appendChild(div)
div.clientLeft // 强制触发回流,使初始状态渲染
div.classList.add('final') // 此时 transition 正常生效内存泄漏与性能优化详见 性能优化专题