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

🧩 函数 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, indexitem, key, idx
分别生成 valuekeyindex 三个变量的 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-ifv-forv-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 智能识别组件节点类型

📘 下一章预告(第五章)

我们将讲解最后的"辅助与优化函数",包括:

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

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

相关推荐
excel5 小时前
Vue 模板编译器核心选项解析:从 Parser 到 Codegen 的全链路设计
前端
excel5 小时前
第三章:指令与属性解析函数组详解
前端
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)
前端