第四章:表达式与循环解析函数详解

80 阅读5分钟

🧩 函数 1:parseForExpression(input: SimpleExpressionNode)

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 的表达式,例如:

<li v-for="(item, index) in items"></li>

会被解析为结构化结果:

{
  "source": "items",
  "value": "item",
  "key": "index",
  "index": null
}

🔍 拆解步骤

步骤说明
提取输入表达式内容,例如 " (item, index) in items "
使用 forAliasRE 正则分离左右两部分(LHS / RHS) → "(item, index)""items"
调用 createAliasExpression() 生成表达式节点
判断 LHS 中是否包含多个变量(item, indexitem, key, idx
分别生成 valuekeyindex 三个变量的 AST 表达式
最终返回 ForParseResult 对象,供上层 v-for 代码生成阶段使用。

🧠 原理剖析

v-for 的解析不仅是语法识别,它还要将表达式的左右两部分正确映射为:

  • 遍历目标source
  • 迭代变量value
  • 可选键名key
  • 可选索引index

这使得编译器在后续生成 render 函数时可以还原成:

_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)

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 && showBabel AST: BinaryExpression( "&&" )
[a,b].map(fn)Babel AST: CallExpression
item of list (Skip 模式)不解析,保持原字符串

🧠 原理讲解

Vue 编译器并不直接执行表达式,而是使用 Babel 进行静态语法树构建,这样可以实现:

  • 静态分析(判断是否常量);
  • 标识符前缀化(作用域隔离);
  • 代码优化(提前折叠常量)。

🧩 函数 3:dirToAttr(dir: DirectiveNode)

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 模式下会被视为:

{
  "type": "ATTRIBUTE",
  "name": "v-bind:id",
  "value": "'foo'"
}

💡 应用场景

  • v-pre:关闭指令编译;
  • 模板兼容模式(老 Vue2 模板中保留 v- 指令原样)。

🧩 函数 4:isFragmentTemplate(el)

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-ifv-forv-slot 等控制结构模板。


📘 举例

<template v-if="ok">...</template>

→ 会被标识为 Fragment Template,特殊对待(不会生成真实 DOM 节点)。


🧩 函数 5:isComponent(el)

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 枚举

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智能识别组件节点类型

📘 下一章预告(第五章)
我们将讲解最后的“辅助与优化函数”,包括:

  • 空白管理(condenseWhitespacecondense
  • 节点位置控制(setLocEndgetLoc
  • 错误恢复机制
  • 命名空间与 XML 模式控制

并在最后总结整个 Vue 解析器的架构逻辑与运行模型。