概念:预标记

预标记(Pre-marking)是 Vue3 编译器在模板编译阶段对动态节点进行标记的优化技术,帮助运行时更高效地进行 diff 对比。

解决的核心痛点:运行时需要遍历整个 VNode 树才能确定哪些是动态节点,预标记提前标记减少遍历开销


核心命题

  • 动态节点类型
    • 插值节点:{{ msg }}
    • 动态绑定::class, :style
    • 动态指令:v-if, v-for, v-bind
  • 标记的作用
    • 快速定位动态节点
    • 减少 diff 时的遍历范围
    • 与 Block 配合实现精准更新

运行机制

当数据变化触发重新渲染时,Vue 3 不会去对比整个虚拟 DOM 树,而是利用 patchFlag 进行优化:

  • 收集动态节点:每个Block(如根节点、带有 v-if/v-for 的节点)会收集其下所有带 patchFlag 的后代动态节点,存储到 dynamicChildren 数组中。

  • 跳过静态树:更新时,直接遍历 dynamicChildren 数组,只对这些动态节点进行对比。

  • 精准更新属性:对于动态节点,根据其 patchFlag 的具体值,只对比相应的属性。例如,如果一个节点只有 TEXT 标记,那就只对比文本内容,不再检查 classstyle 或其他属性。

flowchart TB
    subgraph 模板 ["模板阶段"]
        A["模板<br/><div><span v-if='show'>动态</span></div>"]
    end

    subgraph 预标记 ["预标记阶段"]
        A --> B[AST遍历]
        B --> C{v-if/v-for/插值?}
        C -->|是| D[标记 IS_DYNAMIC]
        C -->|否| E[标记 IS_STATIC]
    end

    subgraph 生成代码 ["生成代码"]
        D --> F[带有标记位的VNode]
    end

    style D fill:#c8e6c9

代码示例

// 模板
<div>
  <span>静态</span>
  <span>{{ msg }}</span>
</div>
 
// 编译后的 VNode(带标记)
// 静态节点:标记为 -1 (HOISTED)
// 动态文本节点:标记为 1 (TEXT)
// 动态元素节点:标记为 2 (CLASS/STYLE 等)
 
function render(_ctx, _cache) {
  return (_openBlock(), _createElementBlock("div", null, [
    // 静态节点,不参与 diff
    _createElementVNode("span", null, "静态", -1),
    // 动态节点,带 TEXT 标记
    _createElementVNode("span", null, _ctx.msg, 1 /* TEXT */)
  ]))
}

标记类型

标记说明
HOISTED-1已静态提升的节点
TEXT1文本内容是动态的
CLASS2class 是动态的
STYLE4style 是动态的
PROPS8属性是动态的
FULL_PROPS16全部属性是动态的

与静态提升的区别

维度静态提升预标记
目的减少创建减少 diff 遍历
处理对象完全静态的节点动态节点
阶段编译时编译时 + 运行时

知识图谱


参考延伸

  • Vue3 源码:packages/compiler-core/src/ast.ts