🧩 函数 1:parseForExpression(input: SimpleExpressionNode)
ini
function parseForExpression(
input: SimpleExpressionNode,
): ForParseResult | undefined {
const loc = input.loc
const exp = input.content
const inMatch = exp.match(forAliasRE)
if (!inMatch) return
const [, LHS, RHS] = inMatch
const createAliasExpression = (
content: string,
offset: number,
asParam = false,
) => {
const start = loc.start.offset + offset
const end = start + content.length
return createExp(
content,
false,
getLoc(start, end),
ConstantTypes.NOT_CONSTANT,
asParam ? ExpParseMode.Params : ExpParseMode.Normal,
)
}
const result: ForParseResult = {
source: createAliasExpression(RHS.trim(), exp.indexOf(RHS, LHS.length)),
value: undefined,
key: undefined,
index: undefined,
finalized: false,
}
let valueContent = LHS.trim().replace(stripParensRE, '').trim()
const trimmedOffset = LHS.indexOf(valueContent)
const iteratorMatch = valueContent.match(forIteratorRE)
if (iteratorMatch) {
valueContent = valueContent.replace(forIteratorRE, '').trim()
const keyContent = iteratorMatch[1].trim()
let keyOffset: number | undefined
if (keyContent) {
keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length)
result.key = createAliasExpression(keyContent, keyOffset, true)
}
if (iteratorMatch[2]) {
const indexContent = iteratorMatch[2].trim()
if (indexContent) {
result.index = createAliasExpression(
indexContent,
exp.indexOf(indexContent, keyOffset! + keyContent.length),
true,
)
}
}
}
if (valueContent) {
result.value = createAliasExpression(valueContent, trimmedOffset, true)
}
return result
}
📖 功能说明
负责解析 v-for 的表达式,例如:
ini
<li v-for="(item, index) in items"></li>
会被解析为结构化结果:
json
{
"source": "items",
"value": "item",
"key": "index",
"index": null
}
🔍 拆解步骤
| 步骤 | 说明 |
|---|---|
| ① | 提取输入表达式内容,例如 " (item, index) in items " |
| ② | 使用 forAliasRE 正则分离左右两部分(LHS / RHS) → "(item, index)" 与 "items" |
| ③ | 调用 createAliasExpression() 生成表达式节点 |
| ④ | 判断 LHS 中是否包含多个变量(item, index、item, key, idx) |
| ⑤ | 分别生成 value、key、index 三个变量的 AST 表达式 |
| ⑥ | 最终返回 ForParseResult 对象,供上层 v-for 代码生成阶段使用。 |
🧠 原理剖析
v-for 的解析不仅是语法识别,它还要将表达式的左右两部分正确映射为:
- 遍历目标 (
source) - 迭代变量 (
value) - 可选键名 (
key) - 可选索引 (
index)
这使得编译器在后续生成 render 函数时可以还原成:
javascript
_renderList(items, (item, index) => ...)
📘 举例说明
| 模板表达式 | 输出结构 |
|---|---|
v-for="item in list" |
{ value: item, source: list } |
v-for="(a, b) in obj" |
{ value: a, key: b, source: obj } |
v-for="(x, y, z) in map" |
{ value: x, key: y, index: z, source: map } |
🧩 函数 2:createExp(content, isStatic, loc, constType, parseMode)
ini
function createExp(
content: SimpleExpressionNode['content'],
isStatic: SimpleExpressionNode['isStatic'] = false,
loc: SourceLocation,
constType: ConstantTypes = ConstantTypes.NOT_CONSTANT,
parseMode = ExpParseMode.Normal,
) {
const exp = createSimpleExpression(content, isStatic, loc, constType)
if (
!__BROWSER__ &&
!isStatic &&
currentOptions.prefixIdentifiers &&
parseMode !== ExpParseMode.Skip &&
content.trim()
) {
if (isSimpleIdentifier(content)) {
exp.ast = null // fast path
return exp
}
try {
const plugins = currentOptions.expressionPlugins
const options: BabelOptions = {
plugins: plugins ? [...plugins, 'typescript'] : ['typescript'],
}
if (parseMode === ExpParseMode.Statements) {
exp.ast = parse(` ${content} `, options).program
} else if (parseMode === ExpParseMode.Params) {
exp.ast = parseExpression(`(${content})=>{}`, options)
} else {
exp.ast = parseExpression(`(${content})`, options)
}
} catch (e: any) {
exp.ast = false
emitError(ErrorCodes.X_INVALID_EXPRESSION, loc.start.offset, e.message)
}
}
return exp
}
📖 功能说明
将字符串形式的表达式转为 Babel AST 对象(用于进一步分析与静态优化)。
🔍 拆解步骤
| 步骤 | 描述 |
|---|---|
| ① | 调用 createSimpleExpression() 创建基础表达式节点。 |
| ② | 若当前启用了 prefixIdentifiers(即启用作用域变量分析),则尝试使用 Babel 解析表达式。 |
| ③ | 根据不同模式(普通表达式、参数模式、语句模式)选择解析方式: → parseExpression() 或 parse()。 |
| ④ | 捕获 Babel 抛出的语法错误,通过 emitError() 上报。 |
| ⑤ | 返回表达式节点对象。 |
💡 parseMode 的含义
| 模式 | 场景 | 行为 |
|---|---|---|
Normal |
普通表达式 | (exp) |
Params |
参数列表(如 v-slot="(a,b)") |
(exp)=>{} |
Statements |
多语句(如 v-on="a();b()") |
parse() |
Skip |
跳过解析(如 v-for) |
不做 Babel 处理 |
📘 举例
| 输入 | 解析后结果 |
|---|---|
ok && show |
Babel AST: BinaryExpression( "&&" ) |
[a,b].map(fn) |
Babel AST: CallExpression |
item of list (Skip 模式) |
不解析,保持原字符串 |
🧠 原理讲解
Vue 编译器并不直接执行表达式,而是使用 Babel 进行静态语法树构建,这样可以实现:
- 静态分析(判断是否常量);
- 标识符前缀化(作用域隔离);
- 代码优化(提前折叠常量)。
🧩 函数 3:dirToAttr(dir: DirectiveNode)
lua
function dirToAttr(dir: DirectiveNode): AttributeNode {
const attr: AttributeNode = {
type: NodeTypes.ATTRIBUTE,
name: dir.rawName!,
nameLoc: getLoc(
dir.loc.start.offset,
dir.loc.start.offset + dir.rawName!.length,
),
value: undefined,
loc: dir.loc,
}
if (dir.exp) {
const loc = dir.exp.loc
if (loc.end.offset < dir.loc.end.offset) {
loc.start.offset--
loc.end.offset++
}
attr.value = {
type: NodeTypes.TEXT,
content: (dir.exp as SimpleExpressionNode).content,
loc,
}
}
return attr
}
📖 功能说明
将指令(DirectiveNode)转换为普通属性节点(AttributeNode)。
用于 v-pre 等场景下"退化处理"。
📘 举例
v-bind:id="'foo'" 在 v-pre 模式下会被视为:
json
{
"type": "ATTRIBUTE",
"name": "v-bind:id",
"value": "'foo'"
}
💡 应用场景
v-pre:关闭指令编译;- 模板兼容模式(老 Vue2 模板中保留 v- 指令原样)。
🧩 函数 4:isFragmentTemplate(el)
javascript
const specialTemplateDir = new Set(['if', 'else', 'else-if', 'for', 'slot'])
function isFragmentTemplate({ tag, props }: ElementNode): boolean {
if (tag === 'template') {
for (let i = 0; i < props.length; i++) {
if (props[i].type === NodeTypes.DIRECTIVE &&
specialTemplateDir.has((props[i] as DirectiveNode).name)) {
return true
}
}
}
return false
}
📖 功能说明
判断 <template> 是否是"片段模板",如 v-if、v-for、v-slot 等控制结构模板。
📘 举例
arduino
<template v-if="ok">...</template>
→ 会被标识为 Fragment Template,特殊对待(不会生成真实 DOM 节点)。
🧩 函数 5:isComponent(el)
java
function isComponent({ tag, props }: ElementNode): boolean {
if (currentOptions.isCustomElement(tag)) return false
if (
tag === 'component' ||
isUpperCase(tag.charCodeAt(0)) ||
isCoreComponent(tag) ||
(currentOptions.isBuiltInComponent &&
currentOptions.isBuiltInComponent(tag)) ||
(currentOptions.isNativeTag && !currentOptions.isNativeTag(tag))
) {
return true
}
...
return false
}
📖 功能说明
识别当前标签是否是 Vue 组件。
🔍 判定规则
| 条件 | 示例 | 判断结果 |
|---|---|---|
| 自定义元素 | <my-element> |
❌(由浏览器识别) |
<component> 标签 |
✅ | |
| 首字母大写 | <HelloWorld> |
✅ |
| 核心组件 | <Transition> |
✅ |
| 内建组件 | <KeepAlive> |
✅ |
使用 :is 动态绑定组件 |
<div :is="'Custom'"> |
✅(兼容模式下) |
🧠 原理
Vue 使用首字母大写规则 + 注册信息判断节点语义类型,这样 <div> 不会被视为组件,而 <MyDiv> 会。
🧩 函数 6:ExpParseMode 枚举
vbnet
enum ExpParseMode {
Normal,
Params,
Statements,
Skip,
}
| 模式 | 用途 | 解析方式 |
|---|---|---|
Normal |
通用表达式 | 包裹在 () 中 |
Params |
参数上下文(如 slot 作用域) | 包裹成箭头函数 |
Statements |
多语句(如 v-on 内多表达式) | 交给 Babel.parse() |
Skip |
跳过 Babel 解析(如 v-for) | 保留字符串 |
✅ 本章总结
本章讲解了 Vue 解析器最"智能"的部分:
它不仅能识别语法结构,还能理解表达式含义,并用 Babel 进行结构化语法树分析。
| 函数 | 作用 |
|---|---|
parseForExpression |
解析 v-for 表达式结构 |
createExp |
构造表达式 AST(支持模式化解析) |
dirToAttr |
将指令退化为属性(用于 v-pre) |
isFragmentTemplate |
判断模板片段是否语义化结构 |
isComponent |
智能识别组件节点类型 |
📘 下一章预告(第五章)
我们将讲解最后的"辅助与优化函数",包括:
- 空白管理(
condenseWhitespace、condense) - 节点位置控制(
setLocEnd、getLoc) - 错误恢复机制
- 命名空间与 XML 模式控制
并在最后总结整个 Vue 解析器的架构逻辑与运行模型。