前端大概要知道的 AST

news2025/6/20 8:22:02

认识 AST

定义:在计算机科学中,抽象语法树是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。之所以说语法是“抽象”的,是因为这里的语法并不会表示出真实语法中出现的每个细节。

从定义中我们只需要知道一件事就行,那就是 AST 是一种树形结构,并且是某种代码的一种抽象表示。

在线可视化网站:https://astexplorer.net/ ,利用这个网站我们可以很清晰的看到各种语言的 AST 结构。

estree[1]

estree 就是 es 语法对应的标准 AST,作为一个前端也比较方便理解。我们以官方文档为例

https://github.com/estree/estree/blob/master/es5.md

  1. 下面看一个代码

console.log('1')

AST 为

{
  "type": "Program",
  "start": 0, // 起始位置
  "end": 16, // 结束位置,字符长度
  "body": [
    {
      "type": "ExpressionStatement", // 表达式语句
      "start": 0,
      "end": 16,
      "expression": {
        "type": "CallExpression", // 函数方法调用式
        "start": 0,
        "end": 16,
        "callee": {
          "type": "MemberExpression", // 成员表达式 console.log
          "start": 0,
          "end": 11,
          "object": {
            "type": "Identifier", // 标识符,可以是表达式或者结构模式
            "start": 0,
            "end": 7,
            "name": "console"
          },
          "property": {
            "type": "Identifier", 
            "start": 8,
            "end": 11,
            "name": "log"
          },
          "computed": false, // 成员表达式的计算结果,如果为 true 则是 console[log], false 则为 console.log
          "optional": false
        },
        "arguments": [ // 参数
          {
            "type": "Literal", // 文字标记,可以是表达式
            "start": 12,
            "end": 15,
            "value": "1",
            "raw": "'1'"
          }
        ],
        "optional": false
      }
    }
  ],
  "sourceType": "module"
}
  1. 看两个稍微复杂的代码

const b = { a: 1 };
const { a } = b;
function add(a, b) {
    return a + b;
}

这里建议读者自己将上述代码复制进上面提到的网站中,自行理解 estree 的各种节点类型。当然了,我们也不可能看一篇文章就记住那么多类型,只要心里有个大致的概念即可。

认识 acorn[2]

由 JavaScript 编写的 JavaScript 解析器,类似的解析器还有很多,比如 Esprima[3] 和 Shift[4] ,关于他们的性能,Esprima 的官网给了个测试地址[5],但是由于 acron 代码比较精简,且 webpack 和 eslint 都依赖 acorn,因此我们这次从 acorn 下手,了解如何使用 AST。

 

基本操作

acorn 的操作很简单

import * as acorn from 'acorn';
const code = 'xxx';
const ast = acorn.parse(code, options)

这样我们就能拿到代码的 ast 了,options 的定义如下

  interface Options {
    ecmaVersion: 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 2015 | 2016 | 2017 | 2018 | 2019 | 2020 | 2021 | 2022 | 'latest'
    sourceType?: 'script' | 'module'
    onInsertedSemicolon?: (lastTokEnd: number, lastTokEndLoc?: Position) => void
    onTrailingComma?: (lastTokEnd: number, lastTokEndLoc?: Position) => void
    allowReserved?: boolean | 'never'
    allowReturnOutsideFunction?: boolean
    allowImportExportEverywhere?: boolean
    allowAwaitOutsideFunction?: boolean
    allowSuperOutsideMethod?: boolean
    allowHashBang?: boolean
    locations?: boolean
    onToken?: ((token: Token) => any) | Token[]
    onComment?: ((
      isBlock: boolean, text: string, start: number, end: number, startLoc?: Position,
      endLoc?: Position
    ) => void) | Comment[]
    ranges?: boolean
    program?: Node
    sourceFile?: string
    directSourceFile?: string
    preserveParens?: boolean
  }
  • ecmaVersion ECMA 版本,默认时 es7

  • locations 默认为 false,设置为 true 时节点会携带一个 loc 对象来表示当前开始与结束的行数。

  • onComment 回调函数,每当代码执行到注释的时候都会触发,可以获取当前的注释内容

获得 ast 之后我们想还原之前的函数怎么办,这里可以使用 astring[6]

import * as astring from 'astring';

const code = astring.generate(ast);

实现普通函数转换为箭头函数

接下来我们就可以利用 AST 来实现一些字符串匹配不太容易实现的操作,比如将普通函数转化为箭头函数。

我们先来看两个函数的AST有什么区别

function add(a, b) {
    return a + b;
}
const add = (a, b) => {
    return a + b;
}
{
  "type": "Program",
  "start": 0,
  "end": 41,
  "body": [
    {
      "type": "FunctionDeclaration",
      "start": 0,
      "end": 40,
      "id": {
        "type": "Identifier",
        "start": 9,
        "end": 12,
        "name": "add"
      },
      "expression": false,
      "generator": false,
      "async": false,
      "params": [
        {
          "type": "Identifier",
          "start": 13,
          "end": 14,
          "name": "a"
        },
        {
          "type": "Identifier",
          "start": 16,
          "end": 17,
          "name": "b"
        }
      ],
      "body": {
        "type": "BlockStatement",
        "start": 19,
        "end": 40,
        "body": [
          {
            "type": "ReturnStatement",
            "start": 25,
            "end": 38,
            "argument": {
              "type": "BinaryExpression",
              "start": 32,
              "end": 37,
              "left": {
                "type": "Identifier",
                "start": 32,
                "end": 33,
                "name": "a"
              },
              "operator": "+",
              "right": {
                "type": "Identifier",
                "start": 36,
                "end": 37,
                "name": "b"
              }
            }
          }
        ]
      }
    }
  ],
  "sourceType": "module"
}
{
  "type": "Program",
  "start": 0,
  "end": 43,
  "body": [
    {
      "type": "VariableDeclaration",
      "start": 0,
      "end": 43,
      "declarations": [
        {
          "type": "VariableDeclarator",
          "start": 6,
          "end": 43,
          "id": {
            "type": "Identifier",
            "start": 6,
            "end": 9,
            "name": "add"
          },
          "init": {
            "type": "ArrowFunctionExpression",
            "start": 12,
            "end": 43,
            "id": null,
            "expression": false,
            "generator": false,
            "async": false,
            "params": [
              {
                "type": "Identifier",
                "start": 13,
                "end": 14,
                "name": "a"
              },
              {
                "type": "Identifier",
                "start": 16,
                "end": 17,
                "name": "b"
              }
            ],
            "body": {
              "type": "BlockStatement",
              "start": 22,
              "end": 43,
              "body": [
                {
                  "type": "ReturnStatement",
                  "start": 28,
                  "end": 41,
                  "argument": {
                    "type": "BinaryExpression",
                    "start": 35,
                    "end": 40,
                    "left": {
                      "type": "Identifier",
                      "start": 35,
                      "end": 36,
                      "name": "a"
                    },
                    "operator": "+",
                    "right": {
                      "type": "Identifier",
                      "start": 39,
                      "end": 40,
                      "name": "b"
                    }
                  }
                }
              ]
            }
          }
        }
      ],
      "kind": "const"
    }
  ],
  "sourceType": "module"
}

找到区别之后我们就可以有大致的思路

  1. 找到 FunctionDeclaration

  1. 将其替换为VariableDeclaration VariableDeclarator 节点

  1. 在 VariableDeclarator 节点的 init 属性下新建 ArrowFunctionExpression 节点

  1. 并将 FunctionDeclaration 节点的相关属性替换到 ArrowFunctionExpression 上即可

但是由于 acorn 处理的 ast 只是单纯的对象,并不具备类似 dom 节点之类的对节点的操作能力,如果需要操作节点,需要写很多工具函数, 所以我这里就简单写一下。

import * as acorn from "acorn";
import * as astring from 'astring';
import { createNode, walkNode } from "./utils.js";

const code = 'function add(a, b) { return a+b; } function dd(a) { return a + 1 }';
console.log('in:', code);
const ast = acorn.parse(code);

walkNode(ast, (node) => {
    if(node.type === 'FunctionDeclaration') {
        node.type = 'VariableDeclaration';
        const variableDeclaratorNode = createNode('VariableDeclarator');
        variableDeclaratorNode.id = node.id;
        delete node.id;
        const arrowFunctionExpressionNode = createNode('ArrowFunctionExpression');
        arrowFunctionExpressionNode.params = node.params;
        delete node.params;
        arrowFunctionExpressionNode.body = node.body;
        delete node.body;
        variableDeclaratorNode.init = arrowFunctionExpressionNode;
        node.declarations = [variableDeclaratorNode];
        node.kind = 'const';
    }
})

console.log('out:', astring.generate(ast))

结果如下

如果想要代码更加健壮,可以使用 recast[7],提供了对 ast 的各种操作

// 用螺丝刀解析机器
const ast = recast.parse(code);

// ast可以处理很巨大的代码文件
// 但我们现在只需要代码块的第一个body,即add函数
const add  = ast.program.body[0]

console.log(add)

// 引入变量声明,变量符号,函数声明三种“模具”
const {variableDeclaration, variableDeclarator, functionExpression} = recast.types.builders

// 将准备好的组件置入模具,并组装回原来的ast对象。
ast.program.body[0] = variableDeclaration("const", [
  variableDeclarator(add.id, functionExpression(
    null, // Anonymize the function expression.
    add.params,
    add.body
  ))
]);

//将AST对象重新转回可以阅读的代码
const output = recast.print(ast).code;

console.log(output)

这里只是示例代码,展示 recast 的一些操作,最好的情况还是能遍历节点自动替换。

这样我们就完成了将普通函数转换成箭头函数的操作,但 ast 的作用不止于此,作为一个前端在工作中可能涉及 ast 的地方,就是自定义 eslint 、 stylelint 等插件。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/411535.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

手机测试—adb

一、Android Debug Bridge 1.1 Android系统主要的目录 1.2 ADB工具介绍 ADB的全称为Android Debug Bridge,就是起到调试桥的作用,是Android SDK里面一个多用途调试工具,通过它可以和Android设备或模拟器通信,借助adb工具,我们可以管理设备或手机模拟器的状态。还可以进行很多…

【负荷预测】基于VMD-SSA-LSTM光伏功率预测【可以换数据变为其他负荷等预测】(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

文件小注意

目录 0 前言 1 标识 O_CREAT O_APPEND 2 ftruncate与truncate 3 O_DIRECT与O_DSYNC、O_SYNC 4 open与fopen 5 关于mmap 0 前言 文件操作在软件开发中是很常见的一件事。虽然与它相关的工作看起来不怎么起眼,无非就是通过通过open、read、write、close几个调用…

Unity——网格变形(制作一个压力球)

主要参考链接:Mesh Deformation, a Unity C# Tutorial(本文为其翻译版) unity项目下载链接:https://download.csdn.net/download/weixin_43042683/87679832 在物体上投射射线并画出调试线。将力转换为顶点的速度。用弹簧和阻尼保…

Rust社区引发舆论危机,问题到底出在哪儿?

围绕开源的法律问题,讨论焦点往往集中在开源许可证、软件著作权等方面,商标的讨论却极少引人关注。事实上,关于开源软件以及开源软件的衍生产品的商标使用情况往往处于某种灰色地带。 最近,Rust基金会正在就更新的商标政策征求反馈…

windows命令执行的几种绕过方法

windows命令执行的几种绕过方法介绍1、添加特殊符号2、定义变量3、切割字符串4、逻辑运算符在绕过中的作用5、利用for循环拼接命令介绍 反检测、反清理,是红队攻击中的重中之重,本文详细描述了几种windows执行命令的几种绕过手法。 1、添加特殊符号 w…

ERP软件的作用

ERP软件的运用是在企业管理系统的数据基础上实现的,它的应用涉及到企业的各个部门。ERP软件是在制造资源计划的基础上进一步发展而成的对企业供应链的管理软件。ERP是集采购、销售和库存、财务、生产管理和委托加工为一体的企业管理软件。它是集企业管理理念、业务流…

带你玩转Python爬虫(胆小者勿进)千万别做坏事·······

这节课很危险,哈哈哈哈,逗你们玩的 目录 写在前面 1 了解robots.txt 1.1 基础理解 1.2 使用robots.txt 2 Cookie 2.1 两种cookie处理方式 3 常用爬虫方法 3.1 bs4 3.1.1 基础介绍 3.1.2 bs4使用 3.1.2 使用例子 3.2 xpath 3.2.1 xpath基础介…

【计算机图形学】扫描转换算法(Bresenham1/4圆法 椭圆两头逼近法 方形刷子)

一 实验目的 编写弧线的光栅扫描转换算法,并对线宽与线形的算法加以探讨熟悉圆和椭圆画线的算法二 实验算法理论分析Bresenham法(1/4圆): 椭圆扫描转换——两头逼近法: 处理线宽问题: 方形刷子宽度存在的…

JS内置对象1

JS中的对象分为:自定义对象、内置对象、浏览器对象内置对象:JS语言自带的一些对象,已经提高最基本的功能常见内置对象:Math、Date、Array、String学习内置对象可通过查阅文档,即MDN/W3C来查阅 …

3.1 微分中值定理

思维导图: 学习目标: 我会按照以下步骤来学习微分中值定理: 理解导数的定义和性质:在学习微分中值定理之前,首先要对导数的定义和性质有一个清晰的理解,包括导数的几何意义和导数存在的条件等。学习拉格朗…

作为大学生,你还不会搭建chatGPT微应用吗?

目录 引言ChatGPT是什么?背景:ChatGPT敢为人先,打破全球僵局示例演示:基于ChatGPT微应用实现的条件及步骤(1)整体框架(2)搭建前的准备工作(3)实际搭建步骤&a…

算法之搜索专题

搜索 深度优先搜索(DFS)和广度优先搜索(BFS)都是常见的图搜索算法。它们的目的是访问和遍历图中的所有节点,并找到满足特定条件的节点。虽然这两种算法的实现方式不同,但它们都有以下的特点。 首先&#…

或许能用 ChatGPT 插件实现财富自由

文章目录或许能用 ChatGPT 插件实现财富自由1. 认识一下1.1 是什么1.2 怎么用2. 举个例2.1 Wolfram2.2 Browsing3. 怎么做到的4. 财富自由4.1 生活类插件4.2 品牌推广类5. 限制或许能用 ChatGPT 插件实现财富自由 我们知道,当前 ChatGPT 最大的局限性就是模型训练数…

如何编写一个自己的web前端脚手架

脚手架简介 脚手架是创建前端项目的命令行工具,集成了常用的功能和配置,方便我们快速搭建项目,目前网络上也有很多可供选择的脚手架。 一个"简单脚手架"的构成其实非常少,即 代码模板 命令行工具。其中代码模板是脚手…

【李宏毅】-生成对抗式网络(GAN)

生成对抗式网络GAN 1. Network as Generator 输入不再是只是x,还有一个simple distribution(样本分布),输出也是一个分布 Why distribution 不同的分布即意味着:相同的输入会有不同的输出。 尤其在任务需要创造力的时…

Win+VisualStudio+vcpkg+Zeromq安装方法

1. 缘由 因为工作上要用到Windows上的zeromq来收发消息,所以我在网上搜集了一些资料最终成功地在Visual Studio2022中用c的libzmq库实现了zmq的收发。 2. 基本资料 2.1 ZeroMQ基本介绍 ZeroMQ官网介绍,因为我也不是专门搞网络和通信的,就…

RocketMQ 事务消息 详解

🍊 Java学习:Java从入门到精通总结 🍊 深入浅出RocketMQ设计思想:深入浅出RocketMQ设计思想 🍊 绝对不一样的职场干货:大厂最佳实践经验指南 📆 最近更新:2023年4月9日 &#x1…

VMware ESXi 8.0c - 领先的裸机 Hypervisor (sysin Custom Image)

本站发布 Dell 和 HPE 定制版 ESXi 8.0c 镜像 请访问原文链接:https://sysin.org/blog/vmware-esxi-8/,查看最新版。原创作品,转载请保留出处。 作者主页:sysin.org 产品简介 VMware ESXi:专门构建的裸机 Hyperviso…

【MATLAB数学建模编程实战】Kmeans算法编程及算法的简单原理

欢迎关注,本专栏主要更新MATLAB仿真、界面、基础编程、画图、算法、矩阵处理等操作,拥有丰富的实例练习代码,欢迎订阅该专栏!(等该专栏建设成熟后将开始收费,快快上车吧~~) 【MATLAB数学建模编…