「我明明写了 element.textContent = 4,为什么没看到页面马上变?」
这个问题,其实触到了浏览器渲染机制的核心:
JS 执行 和 页面绘制 并不是同一件事。
一、JS 的确"立即执行"了
当我们写下:
ini
document.querySelector('li:nth-child(2)').textContent = 4;
这段代码会立刻:
- 找到目标
<li>
; - 修改其在 DOM 树 中的文本节点;
- 内存中的 DOM 已经更新为 "4"。
到这里为止:
- JS 执行是同步的;
- DOM 确实被改了;
- 但屏幕上还没"画"出变化。
二、为什么看不到变化?
因为浏览器有一套 批量渲染机制。
它不会在每次 DOM 改动后立即刷新,而是:
等当前 JS 任务执行完后,在这一帧的渲染阶段 统一绘制。
如果此时已错过渲染时机,就会顺延到 下一帧。
三、浏览器的渲染循环(Render Loop)
浏览器的刷新循环大约每 16.6ms 触发一次(对应 60fps),
流程大致是:
阶段 | 内容 |
---|---|
① | 执行 JS(修改 DOM) |
② | JS 任务结束(Event Loop 检查) |
③ | Layout(计算布局) |
④ | Paint + Composite(绘制像素) |
这意味着:
- JS 线程没空时,渲染线程没法绘制;
- 浏览器会等到 JS 任务结束后再绘制;
- 如果 JS 执行时间太长,错过绘制时机,就会推迟到下一帧。
四、一个实验验证一下
ini
const el = document.querySelector('li:nth-child(2)');
el.textContent = 4;
while (true) {} // 无限循环
结果:
页面不会立刻显示"4",因为 JS 主线程被卡死,浏览器无法进入渲染阶段。
五、完整的时间线
css
┌───────────────┐
│ JS 修改 DOM │ ← 同步执行,立即生效(内存)
└──────┬────────┘
↓
[事件循环等待]
↓
┌───────────────┐
│ Layout 阶段 │
│ Paint 阶段 │ ← 浏览器统一绘制
└───────────────┘
阶段 | 线程 | 是否同步 | 屏幕是否更新 |
---|---|---|---|
JS 执行 | JS 主线程 | ✅ 是 | ❌ 否 |
DOM 更新 | 内存结构 | ✅ 是 | ❌ 否 |
渲染绘制 | 渲染线程 | ❌ 否 | ✅ 是 |
六、关键结论
element.textContent = 4
会立即修改 DOM,但不会立即触发渲染。浏览器会在当前 JS 任务结束后、本帧的绘制阶段 统一绘制。
若本帧的绘制时机已过,才会顺延到下一帧。