Skip to content

AST 转换:transform 阶段对 AST 做了哪些优化?

在上一章,baseParse 函数将模板字符串转换成了一个“原始 AST”。这个 AST 忠实地反映了模板的结构,但并没有包含任何优化信息。

如果直接使用这个原始 AST 生成代码,Vue 在运行时(runtime)的 diff 算法将需要遍历每一个 VNode 节点、对比每一个属性,这效率不高。

因此,transform(转换)阶段的核心任务,就是分析这个原始 AST,找出所有“动态”和“静态”的部分,并为 AST 添加“优化信息”。这些信息将指导后续的“代码生成”(codegen)阶段,生成出性能更高的 render 函数。

这个“优化信息”就是 Vue 3 编译优化的核心:Patch Flags(补丁标记)

核心目标:Patch Flags —— 为“变化”添加“标记”

Patch Flags 是一种编译时优化transform 阶段会分析每一个元素节点,并根据它“可能变化”的部分,附加一个数字标记。

  • <div></div>

    • 分析props(属性)是静态的,但**子节点(文本)**是动态的。
    • 标记patchFlag: 1 /* TEXT */
    • 运行时diff 算法看到这个标记,会跳过 props 的对比,对比文本内容。
  • <div :class="cls" style="color: red"></div>

    • 分析style 是静态的,但 class 是动态的。
    • 标记patchFlag: 2 /* CLASS */
    • 运行时diff 算法检查 class 的变化,跳过 stylechildren
  • <div id="foo">bar</div>

    • 分析完全静态propschildren 都不变。
    • 标记patchFlag: -1 /* HOISTED */
    • 运行时:这个节点会被**“提升”。在组件更新时,diff 算法会完全跳过**这个节点。

transform 阶段的首要任务,就是为 VNode 节点(codegenNode)计算并附加上这个 patchFlag

transform()traverseNode():插件化的遍历系统

Vue 通过一个“插件化”的遍历系统来分析 v-ifv-for:props 等复杂情况。

  1. transform() (入口): 这是转换的入口。它负责创建 context(上下文)并注册一系列“转换函数”nodeTransforms)。

  2. traverseNode() (遍历器)transform 调用 traverseNode,对 AST 进行深度优先遍历

  3. nodeTransforms (转换函数)traverseNode 每访问一个节点,都会调用 nodeTransforms 数组中的所有函数,让它们依次对这个节点进行“分析”或“修改”。

这个 nodeTransforms 数组是 Vue 编译优化的核心,它按顺序包含了:

  • transformIf(处理 v-if
  • transformFor(处理 v-for
  • transformText(合并文本节点)
  • transformElement最核心的,分析 props 并生成 patchFlag
  • cacheStatic(处理静态提升)
  • ...等等

核心转换器(nodeTransforms

1. transformIf / transformFor (结构转换)

v-ifv-for 会改变 DOM 的结构。这两个转换函数会重组 AST

transformIf 的工作:

  • 输入 (原始 AST)
    <div v-if="ok">A</div>
    <p v-else-if="no">B</p>
    <span v-else>C</span>
  • transformIf 的转换: 它会将这三个兄弟节点,重组为一个**IF_NODE 节点**,三个节点变为它的 branches(分支):
    javascript
    {
      type: NodeTypes.IF,
      branches: [
        { type: 'IfBranch', condition: 'ok', children: [/* A */] },
        { type: 'IfBranch', condition: 'no', children: [/* B */] },
        { type: 'IfBranch', condition: undefined, children: [/* C */] }
      ]
    }
  • 目的:重组后的 AST 结构更清晰,便于 codegen 阶段将其转换为 JS 的三元表达式 (ok ? A : no ? B : C)。transformFor 同理,它会生成 FOR_NODE,便于 codegen 生成 renderList 函数。

2. transformText (文本合并)

  • 输入 (原始 AST)<div>Hello, !</div>parse 阶段会将其解析为三个独立的兄弟节点: children: [ { Text, 'Hello, ' }, { Interpolation, 'msg' }, { Text, '!' } ]

  • transformText 的转换transformText 会将相邻的文本和插值节点合并为一个 COMPOUND_EXPRESSION(复合表达式)节点: children: [ { Compound, "Hello, " + msg + "!" } ]

  • 目的:优化运行时,将 3 次“创建文本”操作合并为 1 次。

3. transformElement (核心分析器)

这是最重要的转换函数,它在遍历的“退出”阶段执行(即处理完所有子节点后)。

transformElement 的职责是分析当前元素节点的所有 propschildren,并计算出它的 patchFlag

typescript
// transformElement 伪代码
function transformElement(node, context) {
  // 1. 在退出阶段执行...
  return () => {
    // 2. 分析 props 和指令 (v-bind, v-on, v-model...)
    const propsResult = buildProps(node.props, context)
    
    // 3. 分析 children
    const childrenResult = buildChildren(node.children)

    // 4. 汇总所有分析结果,生成“补丁标记”
    const patchFlag = combineFlags(propsResult.flag, childrenResult.flag)
    
    // 5. 【核心】创建 codegenNode (代码生成节点)
    //    它把所有分析结果 (tag, props, children, patchFlag) 
    //    打包成一个 VNodeCall 节点,供 codegen 使用
    node.codegenNode = createVNodeCall(
      context,
      node.tag,
      propsResult.props,
      childrenResult.children,
      patchFlag
    )
  }
}

transformElement生成 patchFlag 的主要函数。

最终优化:cacheStatic (静态提升)

transform 的最后一步是执行静态提升 (Static Hoisting)

在所有转换函数都运行完毕后,cacheStatic 会再次遍历 AST:

  1. 查找:使用 getConstantType 函数,查找那些100% 静态的节点。
  2. 提升:将这些静态节点从 AST 中“剪切”出来,存入 context.hoists 数组中。

示例:

  • 输入模板
    html
    <div>
      <h1>我是静态标题</h1>
      <p>{{ dynamicMsg }}</p>
    </div>
  • codegen 生成的 render 函数 (伪代码)
    javascript
    // 1. "h1" 节点被分析为 100% 静态
    // 2. 它被“提升”到 render 函数之外,只创建一次
    const _hoisted_1 = createVNode("h1", null, "我是静态标题")
    
    function render() {
      return createVNode("div", null, [
        // 3. 在 render 中,直接“复用”这个 VNode
        _hoisted_1, 
        
        // 4. "p" 节点是动态的 (TEXT),在 render 内部创建
        createVNode("p", null, [
          toDisplayString(dynamicMsg)
        ], 1 /* TEXT */) 
      ])
    }

静态提升是 Vue 3 一项重要的性能优化。它使得静态内容在重新渲染时,VNode 不需要被重新创建,而是直接复用 _hoisted_1 常量,实现了极高的更新效率。

总结

transform 阶段是 Vue 编译器的“优化”阶段。它不是一个单一函数,而是一个插件化的遍历系统

  1. 目标:将“原始 AST”转换为“优化的 AST”。
  2. 核心产物Patch Flags。这是 transform 阶段为运行时 diff 算法提供的“优化提示”。
  3. 手段
    • traverseNode:深度优先遍历 AST。
    • nodeTransforms (插件):一系列转换函数,对 AST 进行分析和修改。
      • transformIf/transformFor 负责“结构重组”。
      • transformText 负责“节点合并”。
      • transformElement 负责分析 propschildren生成 patchFlag
  4. 最终优化
    • cacheStatic (静态提升) 将 100% 静态的节点移到 hoists 数组中,以便在 codegen 阶段将其提升为常量,实现运行时复用。

transform 阶段结束时,AST 不再是一个简单的 HTML 结构,而是一个布满了“优化标记”和“代码生成指令”的 VNode 蓝图,等待 codegen 阶段将其转换为 JavaScript。


微信公众号二维码

Last updated: