Vue 虚拟 DOM:从浏览器渲染到性能优化的逐步解析

当页面有一个长列表或复杂表格,JS 一次性渲染完后,浏览器突然卡顿,滚动不流畅?为什么会这样?要理解这个问题,我们需要先了解 浏览器是如何渲染页面的


一、浏览器渲染流程

浏览器渲染流程主要分为以下阶段:

  1. DOM 树构建:解析 HTML,生成 DOM 节点对象
  2. CSSOM 树构建:解析 CSS,计算每个元素的样式
  3. Render Tree:结合 DOM + CSSOM,只包含可见节点和样式信息
  4. Layout(重排 / Reflow):计算节点最终位置和尺寸
    • 触发条件:尺寸、位置、布局属性变化
  5. Paint(重绘 / Repaint):绘制元素像素
    • 触发条件:颜色、背景、字体颜色变化
  6. Composite(合成):GPU 图层合成最终画面
    • 触发条件:transform、opacity 等 GPU 属性变化

JS 主线程和渲染线程互斥,JS 执行期间渲染线程被阻塞,渲染通常在 JS 执行完成后统一进行。


二、重排与重绘触发机制

操作类型 是否触发重排 是否触发重绘 说明
改变元素尺寸 / 位置 / 布局属性 Layout 阶段重新计算,开销大,可能闪烁
改变颜色 / 背景 / 字体颜色 只绘制,不重排,开销中等
transform / opacity GPU 图层处理,开销小
读取布局属性(offsetWidth / offsetHeight / getBoundingClientRect) ✅(同步 Layout) 强制计算 Layout,但不重新生成 Render Tree,Paint 延迟

三、直接操作 DOM 的性能差异

1️⃣ 不读取布局属性

const el1 = document.getElementById('el1');
const el2 = document.getElementById('el2');
const el3 = document.getElementById('el3');

el1.style.width = '100px';
el2.style.width = '150px';
el3.style.width = '200px';

执行过程:

  • JS顺序修改内存DOM属性
  • 浏览器标记节点为dirty(需Layout/Paint)
  • JS执行完毕后,渲染线程统一执行Render Tree -> Layout -> Paint

效果

  • 视觉上直接显示最终状态 (200px)
  • 内部Layout只计算一次,减少CPU开销

核心结论

  • 连续修改DOM,但不读取布局属性,渲染性能最佳

2️⃣ 中间读取布局属性(性能陷阱)

el1.style.width = '100px';
console.log(el1.offsetWidth); // 读取布局属性,强制同步 Layout
el2.style.width = '150px';
console.log(el2.offsetWidth);
el3.style.width = '200px';
console.log(el3.offsetWidth);

执行过程

  • JS 修改 DOM 属性 → 标记节点 dirty
  • 读取布局属性(offsetWidth 等) → 浏览器必须立即同步计算 Layout(重排)
  • 注意:不会重新生成 Render Tree
  • Paint(重绘)仍延迟到 JS 执行完成或下一帧
  • 每次读取都会触发一次同步 Layout

效果

  • 视觉上最终显示 200px
  • 内部 Layout 被计算三次

核心结论

  • 读取布局属性会触发 同步 Layout(重排)
  • 不会重新生成 Render Tree
  • 不会立即触发 Paint(重绘)
  • 多次读取布局属性 → 多次同步 Layout → 性能下降

四、虚拟 DOM 的出现

Vue 提出了 虚拟 DOM(Virtual DOM) 来解决频繁 DOM 操作导致的性能问题。

  • 在内存中创建虚拟 DOM 树(VNode)
    • 描述 DOM 节点及属性,只在 JS 内存中操作
    • 修改虚拟 DOM 不触发浏览器渲染
  • Diff 算法计算新旧虚拟 DOM 差异
    • 找出真正变化的节点(新增、删除、属性变化)
    • 生成最小 DOM 更新操作
  • Patch 到真实 DOM
    • 避免中间多次同步 Layout
    • 页面更新更流畅,CPU 消耗低

核心:先在 JS 内存计算差异,再批量更新 DOM,减少同步 Layout 次数

五、总结

  • 浏览器渲染流程:DOM + CSSOM → Render Tree → Layout → Paint → Composite
  • 重排开销大,重绘次之
  • 连续 DOM 修改 + 不读取布局属性 → Layout 只计算一次
  • 连续 DOM 修改 + 中间读取布局属性 → Layout 多次计算 → 性能下降
  • 虚拟 DOM:先在内存 diff → 最小 patch → 减少同步 Layout → 页面更新流畅

一句话理解:虚拟 DOM 并不是让 DOM 本身更快,而是 减少浏览器内部重复 Layout 计算,提高页面渲染效率。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐