Appearance
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的变化,跳过style和children。
- 分析:
<div id="foo">bar</div>- 分析:完全静态。
props和children都不变。 - 标记:
patchFlag: -1 /* HOISTED */ - 运行时:这个节点会被**“提升”。在组件更新时,
diff算法会完全跳过**这个节点。
- 分析:完全静态。
transform 阶段的首要任务,就是为 VNode 节点(codegenNode)计算并附加上这个 patchFlag。
transform() 与 traverseNode():插件化的遍历系统
Vue 通过一个“插件化”的遍历系统来分析 v-if、v-for、:props 等复杂情况。
transform()(入口): 这是转换的入口。它负责创建context(上下文)并注册一系列“转换函数”(nodeTransforms)。traverseNode()(遍历器):transform调用traverseNode,对 AST 进行深度优先遍历。nodeTransforms(转换函数):traverseNode每访问一个节点,都会调用nodeTransforms数组中的所有函数,让它们依次对这个节点进行“分析”或“修改”。
这个 nodeTransforms 数组是 Vue 编译优化的核心,它按顺序包含了:
transformIf(处理v-if)transformFor(处理v-for)transformText(合并文本节点)transformElement(最核心的,分析props并生成patchFlag)cacheStatic(处理静态提升)- ...等等
核心转换器(nodeTransforms)
1. transformIf / transformFor (结构转换)
v-if 和 v-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 的职责是分析当前元素节点的所有 props 和 children,并计算出它的 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:
- 查找:使用
getConstantType函数,查找那些100% 静态的节点。 - 提升:将这些静态节点从 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 编译器的“优化”阶段。它不是一个单一函数,而是一个插件化的遍历系统。
- 目标:将“原始 AST”转换为“优化的 AST”。
- 核心产物:Patch Flags。这是
transform阶段为运行时diff算法提供的“优化提示”。 - 手段:
traverseNode:深度优先遍历 AST。nodeTransforms(插件):一系列转换函数,对 AST 进行分析和修改。transformIf/transformFor负责“结构重组”。transformText负责“节点合并”。transformElement负责分析props和children,生成patchFlag。
- 最终优化:
cacheStatic(静态提升) 将 100% 静态的节点移到hoists数组中,以便在codegen阶段将其提升为常量,实现运行时复用。
当 transform 阶段结束时,AST 不再是一个简单的 HTML 结构,而是一个布满了“优化标记”和“代码生成指令”的 VNode 蓝图,等待 codegen 阶段将其转换为 JavaScript。
