前端构建(二)——AST介绍

lxf2023-11-28 18:00:02

这是什么?

AST : 全称为 Abstract Syntax Tree,意为抽象语法树,它是源代码语法结构的一种抽象表示。

为什么要了解?

虽然在平常业务中很少接触它,但是它无处不在,无论是代码编译(babel)、打包构建(webpack)、语法检查(eslint)、美化代码(prettier)、模版编译(jsx、.vue)这些都离不开AST。了解和学习AST能够更好去理解上面工具的工作原理,同时也能用来做一些工具去优化开发流程,提升开发效率

浅析AST

传统编译语言中,源代码执行会先经历三个阶段

  • 词法分析阶段:  扫描代码,将字符组成的字符串分解成一个个代码块(词法单元token),vscode代码高亮就是这一步做的
  • 语法分析阶段:  将词法单元流转换成一个由元素逐级嵌套组成的语法结构树,即所谓的抽象语法树
  • 代码生成阶段:将 AST 转换成一系列可执行的机器指令代码

比如代码

  add(a,b)

词法分析,生成tokens列表

前端构建(二)——AST介绍 生成token时候会一行行扫描代码(可看这篇分享由esprima源码tokens生成部分),这时候它会移除空白符,注释,等。最后,整个代码将被分割进一个tokens列表(或者说一维数组)结果实例

前端构建(二)——AST介绍

语法解析,生成AST树

  • 语义分析则是将得到的词汇进行一个立体的组合,确定词语之间的关系
  • 简单来说语法分析是对语句和表达式的识别,是一个递归的过程 具体过程可以看源码解释这里不在阐述 github.com/jamiebuilds…

可以使用这个网站看ast树astexplorer.net/ 前端构建(二)——AST介绍

怎么去用?

三步曲

  1. 调用babel的parse解析成ast
  2. 调用babel的traverse遍历ast节点对相应的节点进行操作
  3. 调用babel的generator生成新的代码

场景1: 表达式编辑器希望点击服务端返回的函数,解析出其中的参数变成输入框给用户输入

找出函数参数
function getCodeArgs(code) {
  let args = []
  try {
    const ast = parse(code)
    const argMap = {}
    traverse(ast, {
      CallExpression(path) {
        path.node.arguments.forEach((node, index) => {
          if (node.type === 'Identifier') {
            argMap[node.name] = {
              name: node.name,
              callExpression: path.node.name,
              argIndex: index,
            }
          }
          if (node.type === 'MemberExpression') {
            const name = node.object.name + '.' + node.property.name
            argMap[name] = {
              name,
              callExpression: path.node.name,
              argIndex: index,
            }
          }
        })
      },
    })
    args = Object.keys(argMap)
  } catch (error) {
    console.log('error', error)
  }
  return args
}
把用户输入的值重新设置回ast相应的节点上
function generatorCodeForMap(code, dataMap) {
  let str = ''
  try {
    const ast = parse(code)

    traverse(ast, {
      CallExpression(path) {
        path.node.arguments.forEach((node) => {
          if (node.type === 'Identifier' && dataMap[node.name]) {
            node.name = `'${dataMap[node.name]}'`
          }
          if (node.type === 'MemberExpression') {
            const name = node.object.name + '.' + node.property.name
            if (dataMap[name]) {
              node.type = 'Identifier'
              node.name = `'${dataMap[name]}'`
            }
          }
        })
      },
    })

    const res = generator(ast, {}, code)
    // 3. generator 将 AST 转回成代码
    str = (res?.code || '').replace(/;(\s+)?$/, '')
  } catch (error) {
    console.log('error', error)
  }
  return str
}

场景2: 四则运算编辑器,用户输入1+(2*3-2)希望输出5

按照上面三步曲来

import forIn from 'lodash/forIn'
import { simpleArithmetic } from 'utils' // 简单两个数字运算
import { parse } from 'acorn'

const IS_NUMBER_REG = /^([-]?)\d+([.]\d+)?$/

const traverseAst = (node) => {
  let res
  switch (node.type) {
    case 'ExpressionStatement':
      res = simpleArithmetic(node.expression.operator, false, traverseAst(node.expression.left), traverseAst(node.expression.right))
      break
    case 'BinaryExpression':
      res = simpleArithmetic(node.operator, false, traverseAst(node.left), traverseAst(node.right))
      break
    case 'UnaryExpression': {
      if (node.argument.type === 'NumericLiteral') {
        res = node.operator + node.argument.value
      } else {
        res = node.operator + traverseAst(node.argument)
      }
      break
    }
    case 'NumericLiteral':
    case 'Literal':
      res = node.value
      break
    default: break
  }
  return res
}

const complexEval = (input) => {
  let str

  try {
    const ast = parse(input)
    str = traverseAst(ast.body[0])
  } catch (error) {
    str = ''
  }

  return +Number(str).toFixed(4)
}

结语

感谢

本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!