vue3中静态提升和patchflag实现
2026年4月13日 11:48
1. 更快的 Virtual DOM (VDOM) - 具体体现
Vue 3 在虚拟 DOM 方面的改进是多方面的,旨在提高渲染效率和减少不必要的计算。
A. 编译时优化 (Compile-time Optimizations)
这是 Vue 3 与 Vue 2 最大的区别之一。Vue 2 的 VDOM diff 过程是在运行时进行的,它需要逐个比较节点和属性。而 Vue 3 的编译器在构建阶段就能分析模板,生成包含“优化提示”的渲染函数。
- 静态提升 (Hoisting/Diff Skipping): 编译器会识别出模板中的静态节点(即内容不会改变的节点),并将它们提取到渲染函数之外。在后续更新时,Vue 完全跳过对这些节点的比较,因为它们永远不会变。
<!-- 模板 -->
<div>
<h1>This is static</h1> <!-- 静态节点 -->
<p>{{ dynamicValue }}</p> <!-- 动态节点 -->
<span>Another static content</span> <!-- 静态节点 -->
</div>
在 Vue 2 中,每次更新 dynamicValue 时,都会对整个 <div> 的所有子节点进行 diff。在 Vue 3 中,<h1> 和 <span> 会被提升,只对 <p> 进行比较,大大减少了工作量。
- Block Tree (块树): Vue 3 会将动态节点组织成一棵“块树”。更新时,只需要遍历这棵更小的动态节点树,而不是整个 VDOM 树。
- Patch Flags (补丁标志): 编译器会给动态节点打上标记(flag),标明该节点哪些部分可能会变化(如文本、class、props、事件监听器等)。在 diff 阶段,Vue 可以根据这些标志跳过不必要的比较,直接执行特定的更新操作。
<!-- 模板 -->
<p :class="className">{{ message }}</p>
编译器会知道这个 <p> 元素可能变化的部分是 class 和文本内容,并打上相应的 flag。更新时就不会去检查它的 id 或其他不变的属性。
B. 更高效的 Diff 算法
虽然核心思想仍是双端 Diff,但 Vue 3 的实现更加优化,尤其是在处理列表更新时。
- 快速路径 (Fast Paths) for List Updates: 对于一些常见的列表更新模式(如在末尾添加元素、替换整个列表等),Vue 3 提供了专门的快速路径算法,避免了复杂的最长递增子序列计算。
- 更精确的移动策略: 在处理列表项顺序改变时,Vue 3 的算法能更精确地判断哪些元素需要移动,哪些可以就地复用,从而减少 DOM 操作次数。
总结 VDOM 性能提升体现:
- 更快的初始渲染: 静态节点提升和块树优化减少了首次渲染的计算量。
- 更快的状态更新: Patch flags 和优化的 Diff 算法减少了状态变更时的比较和更新开销。
- 更少的内存占用: Block tree 结构和静态提升减少了运行时需要跟踪的节点数量。
2.静态提升和pathflag例子
<template>
<div id="app">
<h1 class="title">Welcome to My App</h1>
<p>{{ greeting }}</p>
<ul>
<li>Static Item 1</li>
<li>Static Item 2</li>
<li>{{ dynamicItem }}</li> <!-- 这一项是动态的 -->
</ul>
<button @click="changeGreeting">Change Greeting</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
const greeting = ref('Hello Vue 3!');
const dynamicItem = ref('Dynamic Item 3');
const changeGreeting = () => {
greeting.value = 'Greetings from Vue 3!';
};
</script>
编译器分析和优化过程:
- 识别静态节点:
-
-
<h1>Welcome to My App</h1>:标签名、内容、class属性都不变,是静态节点。 -
<li>Static Item 1</li>和<li>Static Item 2</li>:标签名和内容都不变,是静态节点。 -
<button>:标签名、内容和事件处理器(@click)都不变,是静态节点。
-
- 识别动态节点:
-
-
<p>{{ greeting }}</p>:内容{{ greeting }}是动态的。 -
<li>{{ dynamicItem }}</li>:内容{{ dynamicItem }}是动态的。 -
<div id="app">:虽然id是静态的,但它包含了动态子节点,因此自身是动态的。
-
- 执行静态提升:
-
- 编译器会将上面识别出的静态节点的 VNode 对象创建代码提取出来,放在渲染函数外面,通常赋值给一个变量(比如
_hoisted_1,_hoisted_2等)。这样它们只会被创建一次。
- 编译器会将上面识别出的静态节点的 VNode 对象创建代码提取出来,放在渲染函数外面,通常赋值给一个变量(比如
- 添加 Patch Flags:
-
-
<p>节点:它的内容是动态的,编译器会为其 VNode 添加patchFlag: Text(数值通常是 1)。这告诉运行时,只需要比较和更新它的文本内容。 -
<li>{{ dynamicItem }}</li>节点:它的内容是动态的,同样会添加patchFlag: Text(数值通常是 1)。 -
<ul>节点:它的子节点列表是动态的(因为包含动态的<li>),编译器会为其添加patchFlag: Children(数值通常是 8 或更复杂的组合)。这告诉运行时,需要对其子节点进行 diff。
-
编译后生成的渲染函数(简化示意):
import { createElementVNode as _createElementVNode, createTextVNode as _createTextVNode, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock, toDisplayString as _toDisplayString } from 'vue'
// --- 静态提升的 VNodes ---
// 这些 VNodes 只在模块加载时创建一次,后续渲染直接复用
const _hoisted_1 = /*#__PURE__*/_createElementVNode("h1", { class: "title" }, "Welcome to My App", -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/_createTextVNode("Static Item 1")
const _hoisted_3 = /*#__PURE__*/_createTextVNode("Static Item 2")
const _hoisted_4 = /*#__PURE__*/_createElementVNode("button", { onClick: "changeGreeting" }, "Change Greeting", -1 /* HOISTED */)
// -----------------------------------
function render(_ctx, _cache, $props, $setup, $data, $options) {
// `_ctx` 通常包含 `greeting` 和 `dynamicItem` 等响应式数据
return (_openBlock(), _createBlock("div", { id: "app" },
[
_hoisted_1, // 直接复用,无需 diff
_createElementVNode("p", null, _toDisplayString($setup.greeting), 1 /* TEXT */), // patchFlag: 1
_createElementVNode("ul", null, [
_hoisted_2, // 直接复用,无需 diff
_hoisted_3, // 直接复用,无需 diff
_createElementVNode("li", null, _toDisplayString($setup.dynamicItem), 1 /* TEXT */) // patchFlag: 1
], 16 /* FULL_PROPS */), // patchFlag: 16 (这里可能表示子节点是动态的,需要 diff)
_hoisted_4 // 直接复用,无需 diff
]
))
}
关键点解读:
-
_hoisted_1,_hoisted_2,_hoisted_3,_hoisted_4:这些都是在编译时创建好的静态 VNode 对象。/* HOISTED */注释表明它们被提升了。在运行时,渲染函数直接使用这些对象,而不必每次都重新创建。 -
_createElementVNode("p", ...)和_createElementVNode("li", ...):这些是动态节点,每次渲染时都需要重新创建 VNode。 -
1 /* TEXT */:这就是patchFlag。它告诉运行时,这个 VNode 只需要关心文本内容的变化。当$setup.greeting或$setup.dynamicItem改变时,运行时只需比较新旧文本字符串,然后更新真实 DOM 的textContent,而不需要比较class、id等其他属性。 -
16 /* FULL_PROPS */(或类似的数值):<ul>的patchFlag表明其子节点是动态的,需要进行子节点的 diff。
通过这种方式,Vue 3 在编译时就对模板进行了深度优化,使得运行时的渲染和更新过程更加高效