第三章:指令与属性解析函数组详解

🧩 函数 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 实体,则解码(如 &lt;<)。
调用 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-ifv-forv-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-forv-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 解析属性名 classsrc AttributeNode
onattribend 解析属性值 "foo" TextNode or ExpressionNode

✅ 本章总结

这一章展示了 Vue 模板解析器最关键的部分------语法语义识别

  • 通过回调组合,实现类 HTML 语法的"增量解析";
  • 支持动态参数、修饰符与内嵌表达式;
  • 自动区分指令与普通属性;
  • 兼容 Vue 2.x 行为。

Vue 的模板语法并不是简单的 HTML 扩展,而是通过词法状态机 + 指令语义层的组合机制精确实现的。


📘 下一章预告(第四章)

我们将讲解更底层的表达式与循环结构解析逻辑,包括:

  • parseForExpression()(v-for 拆解)
  • createExp()(表达式 AST 生成)
  • dirToAttr()(指令转属性)
  • isComponent()isFragmentTemplate()(组件识别)
相关推荐
excel5 小时前
📘 Vue 3 模板解析器源码精讲(baseParse.ts)
前端
excel5 小时前
Vue 编译器核心模块结构与导出机制详解
前端
excel5 小时前
第二章:标签与文本节点解析函数组详解
前端
excel5 小时前
Vue 3 编译器源码深度解析:codegen.ts 模块详解
前端
一个假的前端男5 小时前
uniapp vue2 三端瀑布流
前端·javascript·uni-app
excel5 小时前
Vue 编译器中 walkIdentifiers 源码深度解析
前端
excel5 小时前
一文看懂 Vue 编译器里的插槽处理逻辑(buildSlots.ts)
前端
excel5 小时前
Vue 编译器源码解析:错误系统(errors.ts)
前端
余道各努力,千里自同风5 小时前
uni-app 请求封装
前端·uni-app