Skip to content

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.innerHTMLelement.innerHTML
作用范围整个文档整个 body指定元素
加载后调用清空页面重写清空 body 重写只替换目标元素内容
性能触发完全重排触发 body 重排只重排受影响区域
适用场景几乎不用几乎不用动态更新局部内容

结论: document.write()document.body.innerHTML 都会大范围替换内容,应避免使用。针对具体元素的 element.innerHTML 才是正确的局部更新方式。


JS 操作 DOM 是同步还是异步?

结论:JS 操作 DOM 是同步执行的,但看起来像“异步”,原因是:

  1. JS 引擎线程与 GUI 渲染线程互斥:JS 执行期间 GUI 无法渲染,导致 DOM 操作的效果不会立即显示
  2. 浏览器渲染优化策略:将同一 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 正常生效

内存泄漏与性能优化详见 性能优化专题