概念:预标记
预标记(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标记,那就只对比文本内容,不再检查class、style或其他属性。
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 | 已静态提升的节点 |
TEXT | 1 | 文本内容是动态的 |
CLASS | 2 | class 是动态的 |
STYLE | 4 | style 是动态的 |
PROPS | 8 | 属性是动态的 |
FULL_PROPS | 16 | 全部属性是动态的 |
与静态提升的区别
| 维度 | 静态提升 | 预标记 |
|---|---|---|
| 目的 | 减少创建 | 减少 diff 遍历 |
| 处理对象 | 完全静态的节点 | 动态节点 |
| 阶段 | 编译时 | 编译时 + 运行时 |
知识图谱
- 父级概念:模板编译(Vue3)
- 关联概念:
参考延伸
- Vue3 源码:
packages/compiler-core/src/ast.ts