从箭头函数转换看AST抽象语法树(小白级教程,简单易懂)

644 阅读4分钟

前言

今天我们讲解AST,如果只讲理论太抽象了,我们会结合实战,以及面试的考点,以及一些容易理解的特点,来进行讲解

什么是AST(抽象语法树)?

抽象语法树的关键在树🌲上面,简单理解,就是把我们写的代码按照一定的规则转换成一种树形结构

在形象生动一点,就是把我们的代码解析(parse)成一颗树,树上的每一个节点就是代码中的一个字符,然后我们把树上的某些节点中的字符转换(transform)成其他字符,最后再生成(generate)一颗新的树

可以在脑海中想象一颗苹果树,然后你把苹果树的苹果都换成梨,就变成了梨树🌲,😝

面试题:Babel编译原理是什么的

  • babylon 将 ES6/ES7 代码解析成 AST
  • babel-traverse 对 AST 进行遍历转译,得到新的 AST
  • 新 AST 通过 babel-generator 转换成 ES5

AST能做什么?

    - 语法检查、代码风格检查、格式化代码、语法高亮、错误提示、自动补全等\
    - 代码混淆压缩
    - 优化变更代码,改变代码结构等

举个例子

  • 有个函数 function a() {} 我想把它变成 function b() {}
  • 在 webpack 中代码编译完成后 require('a') --> __webapck__require__("*/**/a.js")

还有我们的React的Jsx语法,vue的template等都使用了AST,是不是感觉很强大

实现箭头函数转换成普通函数插件

我们讲东西一定要把抽象的东西具体化,比如抽象语法树就很抽象,我们具体一下,如何使用,如何掌握

我们要实现的例子是这样的

const c=(a,b)=>a+b 

箭头函数转换为普通函数

const c=function(a,b){return a+b};

准备工作

提起ast大家都会想到babel,babel主要是用来把es6代码转换为es5代码的,兼容所有浏览器,此案例我们就使用babel来操作ast

需要使用的依赖

  • @babel/core babel 核心包并不会去转换代码,核心包只提供一些核心 API,真正做转换工作的是(插件和预设),所以说想要转换代码,一定要设置插件或者预设

  • @babel/types babel/types有两个作用

  1. 判断这个节点是不是这个节点(t.isBlockStatement)
  2. 生成对应的表达式(t.blockStatement([r]))

分析ast结构

首先我们通过astexplorer.net/ 这个网站去对比两端代码的区别

QQ20211208-223038@2x.png 两段代码的type类型不一致,要把ArrowFunctionExpression转换为FunctionExpression类型, 如何生成类型,就需要@babel/types了,我们可以到www.babeljs.cn/docs/babel-… 这个网站去查询要转换的类型,需要什么参数

图片.png

再来对比一下普通函数的参数的构成 图片.png

  • id:没有id,传null,
  • params:params和箭头函数的相同,直接传就可以了
  • body:箭头函数的body为BinaryExpression类型,而普通函数为BlockStatement类型,所以还需要生成BlockStatement类型,在查看一下types文档,

图片.png

普通函数的body需要传ReturnStatement类型,

图片.png

而returnStatement的argument和箭头函数的body一直,这就简单多了

  • generator 为false
  • async 为false

那么先来初始化代码

图片.png

访问者模式

经过上面的分析,我们知道了应该如何去写,接下来我们就来实现

我们开发 plugins 的时候要用到访问者模式,就是说在访问到某一个路径的时候进行匹配,然后在对这个节点进行修改,比如说上面的当我们访问到 ArrowFunctionExpression 的时候,对 ArrowFunctionExpression 进行修改,变成普通函数

const arrowTransform = {
    visitor: {
        ArrowFunctionExpression(path) {
            const node = path.node; // 要想获取到params,body,首先要获取到节点node
            const params = node.params;
            const body = node.body;
            const r = t.returnStatement(body);// 用@babel/types生成returnStatement类型,body为普通函数body
            const block = t.blockStatement([r]);// 在生成blockStatement类型,参数为数组
            const f = t.functionExpression(null, params, block, false, false);// 生成functionExpression类型,参数为我们之前约定好的
            path.replaceWith(f); // 只需要替换一下,就把ArrowFunctionExpression类型替换为functionExpression类型
        },
    },
}    

上完整代码 图片.png

结果为

const c = function (a, b) {
    return a + b;
};

是不是很开心

总结

以上我们只是简单的实现了一个箭头函数转换插件,写的也不是很严谨,但是能非常简单的让新手上手,这样我们的目的就达到了,如果此篇文章对你有帮助,请鼠标左上角来个赞,感谢Thanks♪(・ω・)ノ

参考文档