🧩 函数 1:oninterpolation(start, end)
lua
oninterpolation(start, end) {
if (inVPre) {
return onText(getSlice(start, end), start, end)
}
let innerStart = start + tokenizer.delimiterOpen.length
let innerEnd = end - tokenizer.delimiterClose.length
while (isWhitespace(currentInput.charCodeAt(innerStart))) innerStart++
while (isWhitespace(currentInput.charCodeAt(innerEnd - 1))) innerEnd--
let exp = getSlice(innerStart, innerEnd)
if (exp.includes('&')) {
exp = __BROWSER__ ? currentOptions.decodeEntities!(exp, false) : decodeHTML(exp)
}
addNode({
type: NodeTypes.INTERPOLATION,
content: createExp(exp, false, getLoc(innerStart, innerEnd)),
loc: getLoc(start, end),
})
}
📖 功能说明
解析插值表达式 {{ ... }},生成 INTERPOLATION 节点。
🔍 拆解步骤
| 步骤 | 描述 |
|---|---|
| ① | 若当前处于 v-pre 区域(跳过编译),则直接当作文本处理。 |
| ② | 计算插值内表达式的真实起止位置。 |
| ③ | 去除前后空白字符。 |
| ④ | 若表达式中包含 HTML 实体,则解码(如 < → <)。 |
| ⑤ | 调用 createExp() 构建表达式节点。 |
| ⑥ | 调用 addNode() 插入到当前父节点的 children 中。 |
🧠 设计原理
createExp() 会尝试将 {{ message }} 转换为:
json
{
"type": 5,
"content": { "type": 4, "content": "message", "isStatic": false }
}
其中:
- NodeType 5 表示插值;
- NodeType 4 表示简单表达式。
🧩 函数 2:ondirname(start, end)
ini
ondirname(start, end) {
const raw = getSlice(start, end)
const name =
raw === '.' ? 'bind' :
raw === ':' ? 'bind' :
raw === '@' ? 'on' :
raw === '#' ? 'slot' :
raw.slice(2)
...
}
📖 功能说明
解析指令的名称部分,比如 v-if、v-for、v-bind、@click、:src。
🔍 拆解步骤
| 步骤 | 内容 |
|---|---|
| ① | 从模板源中提取原始指令标识(v-、:、@ 等)。 |
| ② | 根据首字符映射到规范化名称: |
:→v-bind@→v-on#→v-slot.→v-bind.prop|
| ③ | 若当前在v-pre模式或指令名为空,则退化为普通属性。 |
| ④ | 否则创建DirectiveNode,初始化指令名、参数、修饰符数组。 |
| ⑤ | 特殊处理v-pre:进入原样渲染模式,记录当前节点边界。 |
📘 举例
bash
<div :id="uid" @click="doSomething" v-if="ok"></div>
对应三个指令节点:
css
[ { "name": "bind", "arg": "id", "exp": "uid" }, { "name": "on", "arg": "click", "exp": "doSomething" }, { "name": "if", "exp": "ok" }]
🧩 函数 3:ondirarg(start, end)
lua
ondirarg(start, end) {
if (start === end) return
const arg = getSlice(start, end)
if (inVPre && !isVPre(currentProp!)) {
(currentProp as AttributeNode).name += arg
setLocEnd((currentProp as AttributeNode).nameLoc, end)
} else {
const isStatic = arg[0] !== `[`
(currentProp as DirectiveNode).arg = createExp(
isStatic ? arg : arg.slice(1, -1),
isStatic,
getLoc(start, end),
isStatic ? ConstantTypes.CAN_STRINGIFY : ConstantTypes.NOT_CONSTANT,
)
}
}
📖 功能说明
解析指令参数,例如:
ruby
<div :id="foo"></div>
<div v-on:[event]="handler"></div>
中的 id 或 [event]。
🧠 原理
Vue 支持两种参数类型:
- 静态参数 :直接字符串,如
:id; - 动态参数 :使用方括号包裹,如
v-bind:[attr]。
ondirarg 会判断是否为动态参数,并调用 createExp() 生成对应的表达式节点。
🧩 函数 4:ondirmodifier(start, end)
lua
ondirmodifier(start, end) {
const mod = getSlice(start, end)
if (inVPre && !isVPre(currentProp!)) {
(currentProp as AttributeNode).name += '.' + mod
setLocEnd((currentProp as AttributeNode).nameLoc, end)
} else if ((currentProp as DirectiveNode).name === 'slot') {
const arg = (currentProp as DirectiveNode).arg
if (arg) {
(arg as SimpleExpressionNode).content += '.' + mod
setLocEnd(arg.loc, end)
}
} else {
const exp = createSimpleExpression(mod, true, getLoc(start, end))
(currentProp as DirectiveNode).modifiers.push(exp)
}
}
📖 功能说明
解析指令修饰符(如 .stop、.sync、.prevent 等)。
🔍 举例
arduino
<button @click.stop.prevent="doSomething"></button>
生成:
json
{
"name": "on",
"arg": "click",
"modifiers": ["stop", "prevent"]
}
💡 特殊情况
v-slot 指令的修饰符实际是作用在参数上的,因此需要特殊拼接处理。
🧩 函数 5:onattribname(start, end)
sql
onattribname(start, end) {
currentProp = {
type: NodeTypes.ATTRIBUTE,
name: getSlice(start, end),
nameLoc: getLoc(start, end),
value: undefined,
loc: getLoc(start),
}
}
📖 功能说明
解析普通属性名(非指令),如:
ini
<img src="logo.png" alt="Logo">
生成:
json
{ "type": "ATTRIBUTE", "name": "src", "value": "logo.png" }
🧩 函数 6:onattribdata(start, end) 与 onattribend(quote, end)
(a)onattribdata
sql
onattribdata(start, end) {
currentAttrValue += getSlice(start, end)
if (currentAttrStartIndex < 0) currentAttrStartIndex = start
currentAttrEndIndex = end
}
逐步累积属性值(例如处理 src="lo...go.png" 时,分多次调用)。
(b)onattribend
ini
onattribend(quote, end) {
if (currentOpenTag && currentProp) {
setLocEnd(currentProp.loc, end)
if (quote !== QuoteType.NoValue) {
if (__BROWSER__ && currentAttrValue.includes('&')) {
currentAttrValue = currentOptions.decodeEntities!(currentAttrValue, true)
}
if (currentProp.type === NodeTypes.ATTRIBUTE) {
if (currentProp.name === 'class') {
currentAttrValue = condense(currentAttrValue).trim()
}
currentProp.value = {
type: NodeTypes.TEXT,
content: currentAttrValue,
loc: getLoc(currentAttrStartIndex, currentAttrEndIndex)
}
} else {
currentProp.exp = createExp(currentAttrValue, false, getLoc(currentAttrStartIndex, currentAttrEndIndex))
}
}
currentOpenTag.props.push(currentProp)
}
currentAttrValue = ''
currentAttrStartIndex = currentAttrEndIndex = -1
}
📖 功能说明
当属性或指令值结束时被调用,用于创建 value 节点或指令表达式。
💡 特殊逻辑
- 对
class属性做空格压缩; - 对空值发出错误提示;
- 对
v-for、v-on等指令调用createExp()生成 Babel AST; - 检查
.sync修饰符以兼容 Vue 2。
🧩 函数 7:oncomment(start, end)
scss
oncomment(start, end) {
if (currentOptions.comments) {
addNode({
type: NodeTypes.COMMENT,
content: getSlice(start, end),
loc: getLoc(start - 4, end + 3),
})
}
}
📖 功能说明
解析注释节点(<!-- comment -->),在启用 comments: true 时保留。
🧩 函数 8:ondir* 系列小结
| 函数 | 作用 | 典型输入 | 输出类型 |
|---|---|---|---|
ondirname |
解析指令名 | v-if、:、@ |
DirectiveNode |
ondirarg |
解析参数 | [attr]、click |
SimpleExpressionNode |
ondirmodifier |
解析修饰符 | .stop、.sync |
数组元素 |
onattribname |
解析属性名 | class、src |
AttributeNode |
onattribend |
解析属性值 | "foo" |
TextNode or ExpressionNode |
✅ 本章总结
这一章展示了 Vue 模板解析器最关键的部分------语法语义识别:
- 通过回调组合,实现类 HTML 语法的"增量解析";
- 支持动态参数、修饰符与内嵌表达式;
- 自动区分指令与普通属性;
- 兼容 Vue 2.x 行为。
Vue 的模板语法并不是简单的 HTML 扩展,而是通过词法状态机 + 指令语义层的组合机制精确实现的。
📘 下一章预告(第四章)
我们将讲解更底层的表达式与循环结构解析逻辑,包括:
parseForExpression()(v-for 拆解)createExp()(表达式 AST 生成)dirToAttr()(指令转属性)isComponent()、isFragmentTemplate()(组件识别)