Static Hoisting(静态提升)

Vue 3 模板编译器的优化策略,将不包含动态绑定的 VNode 提升到渲染函数外部,使其只创建一次并在后续渲染中直接复用,跳过 diff 比较。与 Patch Flags 配合构成 Vue 编译优化的两大支柱。

核心概念

在 Vue 3 的模板编译中,如果一个元素及其所有后代都没有动态绑定(没有 v-bind{{ }}v-if 等),编译器会将该 VNode 的创建代码提升(hoist)到渲染函数外部——变成模块级的常量。

html
<!-- 模板 -->
<div>
  <p>这是静态内容</p>
  <span>{{ dynamicValue }}</span>
</div>
js
// 编译产物(简化)
const _hoisted_1 = createElementVNode("p", null, "这是静态内容", -1 /* HOISTED */)

function render(_ctx) {
  return createElementVNode("div", null, [
    _hoisted_1,  // 直接复用,不重新创建
    createElementVNode("span", null, toDisplayString(_ctx.dynamicValue), 1 /* TEXT */)
  ])
}

_hoisted_1 只在模块加载时创建一次。渲染函数每次重执行时直接引用这个常量,跳过 VNode 创建和 diff 两个阶段——Patch Flag 为 -1HOISTED)告诉 diff 算法"这个节点永远不变,直接跳过"。

优化层次

Vue 3 的静态提升有三个递进的层次:

  1. 元素提升:没有动态绑定的元素整体提升为常量 VNode
  2. 属性提升:元素本身有动态绑定但部分属性是静态的,静态属性对象被提升
  3. 字符串化:连续多个静态元素被合并为一个 createStaticVNode(htmlString) 调用——直接用 innerHTML 一次性创建,比逐个 createElement 更快

第三层是最激进的优化——当连续静态节点数量超过阈值(默认 20 个)时触发。SSR 场景下字符串化直接输出 HTML 字符串,和手写 HTML 性能相当。

与 Patch Flags 的协作

静态提升和 Patch Flags 是互补的两种编译优化:

  • 静态提升解决"哪些节点完全不需要处理"——提升的节点在 diff 时直接跳过
  • Patch Flags解决"需要处理的节点中哪些属性可以跳过"——只检查标记的动态属性

两者配合将 diff 的工作量从 O(模板总节点数 × 属性数) 压缩到 O(动态节点数 × 动态属性数)。对于一个有 100 个节点但只有 5 个动态绑定的模板,diff 工作量缩减了 95%+。

与其他框架的对比

  • React:JSX 没有静态分析能力(JSX 是任意 JavaScript 表达式),无法区分静态和动态内容。React Compiler 的缓存检查是更粗粒度的"跳过"机制,但不等同于静态提升
  • Svelte / Solid:不需要静态提升——它们根本不用 VDOM,静态 HTML 在编译时被提取为 template() 函数直接克隆 DOM,只有动态部分生成更新代码。从效果上看,Svelte/Solid 是"天然的全量静态提升"
  • Angular Ivy:类似思路,模板编译为指令序列,静态部分只创建一次

引用本术语的文章