阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > AST节点的数据结构

AST节点的数据结构

作者:陈川 阅读数:41344人阅读 分类: Vue.js

AST节点的数据结构

AST(抽象语法树)是源代码的树状表示,Vue3的模板编译器会将模板字符串转换为AST,再进行后续的优化和代码生成。AST节点是构成这棵树的基本单元,每个节点都代表模板中的一个结构元素。

基础节点类型

Vue3的AST节点主要分为以下几种基础类型:

  1. 根节点(RootNode):整个AST的根,包含模板的所有内容
  2. 元素节点(ElementNode):对应HTML元素
  3. 文本节点(TextNode):纯文本内容
  4. 插值节点(InterpolationNode):双大括号表达式
  5. 属性节点(AttributeNode):元素的属性
  6. 指令节点(DirectiveNode):以v-开头的特殊属性

节点通用属性

所有AST节点都继承自基础节点接口,包含以下通用属性:

interface BaseNode {
  type: NodeType // 节点类型
  loc: SourceLocation // 源代码位置信息
  __source?: string // 原始代码字符串(开发模式使用)
}

其中NodeType是一个枚举,定义了所有可能的节点类型:

const enum NodeType {
  ROOT,
  ELEMENT,
  TEXT,
  INTERPOLATION,
  ATTRIBUTE,
  DIRECTIVE
}

元素节点详解

元素节点是AST中最复杂的节点类型,其数据结构如下:

interface ElementNode extends BaseNode {
  type: NodeType.ELEMENT
  tag: string // 标签名
  props: (AttributeNode | DirectiveNode)[] // 属性/指令数组
  children: TemplateChildNode[] // 子节点
  isSelfClosing: boolean // 是否自闭合标签
  codegenNode?: CodegenNode // 代码生成阶段使用的节点
}

示例:对于模板<div class="container"><span>hello</span></div>,对应的AST结构大致为:

{
  type: NodeType.ELEMENT,
  tag: 'div',
  props: [
    {
      type: NodeType.ATTRIBUTE,
      name: 'class',
      value: {
        content: 'container',
        loc: { /* 位置信息 */ }
      }
    }
  ],
  children: [
    {
      type: NodeType.ELEMENT,
      tag: 'span',
      props: [],
      children: [
        {
          type: NodeType.TEXT,
          content: 'hello',
          loc: { /* 位置信息 */ }
        }
      ],
      isSelfClosing: false
    }
  ],
  isSelfClosing: false
}

属性与指令节点

属性节点和指令节点都用于描述元素的特性,但结构有所不同:

属性节点

interface AttributeNode extends BaseNode {
  type: NodeType.ATTRIBUTE
  name: string // 属性名
  value: {
    content: string // 属性值内容
    loc: SourceLocation // 值的位置信息
  } | undefined // 可能没有值(如disabled属性)
}

指令节点

interface DirectiveNode extends BaseNode {
  type: NodeType.DIRECTIVE
  name: string // 指令名(不含v-前缀)
  arg: ExpressionNode | undefined // 参数(如v-on:click中的click)
  exp: ExpressionNode | undefined // 表达式(如v-model="message"中的message)
  modifiers: string[] // 修饰符数组(如v-model.trim中的trim)
}

示例:<input v-model.trim="message" @click="handleClick">中的指令节点:

// v-model.trim="message"
{
  type: NodeType.DIRECTIVE,
  name: 'model',
  arg: undefined,
  exp: {
    type: 'SimpleExpression',
    content: 'message',
    isStatic: false
  },
  modifiers: ['trim']
}

// @click="handleClick"
{
  type: NodeType.DIRECTIVE,
  name: 'on',
  arg: {
    type: 'SimpleExpression',
    content: 'click',
    isStatic: true
  },
  exp: {
    type: 'SimpleExpression',
    content: 'handleClick',
    isStatic: false
  },
  modifiers: []
}

表达式节点

指令和插值中使用的表达式会被解析为表达式节点:

interface SimpleExpressionNode extends BaseNode {
  type: 'SimpleExpression'
  content: string // 表达式内容
  isStatic: boolean // 是否是静态表达式
  constType: ConstantTypes // 常量类型
}

interface CompoundExpressionNode extends BaseNode {
  type: 'CompoundExpression'
  children: (
    | SimpleExpressionNode
    | CompoundExpressionNode
    | InterpolationNode
    | TextNode
    | string
  )[]
}

示例:{{ count + 1 }}会被解析为:

{
  type: NodeType.INTERPOLATION,
  content: {
    type: 'SimpleExpression',
    content: 'count + 1',
    isStatic: false,
    constType: 0 // NOT_CONSTANT
  }
}

复合表达式

Vue3支持更复杂的表达式结构,如模板字符串和链式调用:

// 模板字符串
`{{ `Hello ${name}` }}`

// 对应的AST节点
{
  type: 'CompoundExpression',
  children: [
    '`Hello ',
    {
      type: 'SimpleExpression',
      content: 'name',
      isStatic: false
    },
    '`'
  ]
}

静态标记与提升

Vue3的AST节点包含静态分析相关的标记,用于优化:

interface ElementNode extends BaseNode {
  // ...
  tagType: ElementTypes // 元素类型(组件/原生元素/slot等)
  isSelfClosing: boolean
  codegenNode?: CodegenNode
  ssrCodegenNode?: CodegenNode
  // 静态提升相关
  staticCount?: number
  props?: (AttributeNode | DirectiveNode)[]
  patchFlag?: string
  dynamicProps?: string[]
  // 用于SSR优化
  ssrTransform?: boolean
}

示例:静态节点会被标记以便优化:

<div class="static-class">Static content</div>

对应的AST节点会包含:

{
  // ...
  patchFlag: '1', // 静态标记
  staticCount: 1,
  // ...
}

源码中的节点创建

Vue3编译器内部通过工厂函数创建节点:

// packages/compiler-core/src/ast.ts
export function createBaseNode(
  type: NodeType,
  loc: SourceLocation
): BaseNode {
  return {
    type,
    loc,
    __source: ''
  }
}

export function createElementNode(
  tag: string,
  props: (AttributeNode | DirectiveNode)[],
  children: TemplateChildNode[],
  loc: SourceLocation
): ElementNode {
  return {
    ...createBaseNode(NodeType.ELEMENT, loc),
    tag,
    props,
    children,
    isSelfClosing: false,
    codegenNode: undefined
  }
}

节点转换过程

AST节点在编译过程中会经历多次转换:

  1. 原始AST:由parse函数生成的基础AST
  2. 转换AST:经过transform处理后的AST
  3. 代码生成AST:包含代码生成所需信息的最终AST

示例:v-if指令的转换过程:

<div v-if="show">Content</div>

转换后的AST节点会包含条件表达式:

{
  type: NodeType.IF,
  branches: [
    {
      condition: {
        type: 'SimpleExpression',
        content: 'show'
      },
      children: [/* 原始元素节点 */]
    }
  ]
}

特殊节点处理

一些特殊语法会生成特殊节点结构:

v-for指令

interface ForNode extends BaseNode {
  type: NodeType.FOR
  source: ExpressionNode // 迭代对象
  value: ExpressionNode | undefined // 值别名
  key: ExpressionNode | undefined // 键别名
  index: ExpressionNode | undefined // 索引别名
  children: TemplateChildNode[] // 循环内容
  codegenNode?: ForCodegenNode
}

v-slot指令

interface SlotOutletNode extends BaseNode {
  type: NodeType.SLOT
  props: (AttributeNode | DirectiveNode)[]
  children: TemplateChildNode[]
  codegenNode?: CodegenNode
}

节点位置信息

SourceLocation对象记录了节点在源字符串中的精确位置:

interface SourceLocation {
  start: Position // 起始位置
  end: Position // 结束位置
  source: string // 源字符串片段
}

interface Position {
  offset: number // 从0开始的字符偏移量
  line: number // 行号(从1开始)
  column: number // 列号(从0开始)
}

示例:对于模板<div>hello</div>,div元素的location可能是:

{
  start: { offset: 0, line: 1, column: 0 },
  end: { offset: 17, line: 1, column: 17 },
  source: '<div>hello</div>'
}

节点辅助工具

Vue3编译器提供了一些工具函数用于操作AST节点:

// 检查节点类型
export function isElementNode(node: any): node is ElementNode {
  return node.type === NodeType.ELEMENT
}

// 创建简单的表达式节点
export function createSimpleExpression(
  content: string,
  isStatic: boolean,
  loc = locStub
): SimpleExpressionNode {
  return {
    type: 'SimpleExpression',
    loc,
    content,
    isStatic,
    constType: isStatic ? ConstantTypes.CAN_STRINGIFY : ConstantTypes.NOT_CONSTANT
  }
}

// 创建文本节点
export function createTextNode(
  content: string,
  loc: SourceLocation
): TextNode {
  return {
    type: NodeType.TEXT,
    loc,
    content
  }
}

节点遍历与访问

编译器使用访问者模式遍历AST节点:

export function traverseNodes(
  nodes: TemplateChildNode[],
  visitor: NodeVisitor
) {
  for (const node of nodes) {
    // 调用对应的访问方法
    switch (node.type) {
      case NodeType.ELEMENT:
        visitor.visitElement?.(node)
        traverseNodes(node.children, visitor)
        break
      case NodeType.TEXT:
        visitor.visitText?.(node)
        break
      // 其他节点类型...
    }
  }
}

示例访问者实现:

const visitor = {
  visitElement(node: ElementNode) {
    console.log(`访问元素节点: ${node.tag}`)
  },
  visitText(node: TextNode) {
    console.log(`访问文本节点: ${node.content}`)
  }
}

节点与代码生成

AST节点最终会被转换为代码生成节点(CodegenNode),这些节点包含生成渲染函数所需的所有信息:

interface VNodeCall {
  type: NodeType.ELEMENT
  tag: string | symbol
  props: PropsExpression | undefined
  children: TemplateChildNode[] | undefined
  patchFlag: string | undefined
  dynamicProps: string | undefined
  directives: DirectiveNode[] | undefined
  isBlock: boolean
  disableTracking: boolean
}

示例:简单的元素节点会被转换为:

{
  type: NodeType.ELEMENT,
  tag: 'div',
  props: {
    type: 'ObjectExpression',
    properties: [
      {
        key: { type: 'Identifier', name: 'class' },
        value: { type: 'StringLiteral', value: 'container' }
      }
    ]
  },
  children: [/* 子节点 */],
  patchFlag: '1',
  isBlock: false
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌