一、什么是抽象语法树
开发者可以阅读,理解按语法规则书写的的代码,但是对编译器等工具来讲,它能理解的就是抽象语法树(Abstract Syntax Tree)。即按语法规则书写的源代码,能被编译器理解的抽象结构。它以树状的形式表现编程语言的语法结构。
大致了解编译原理:
二、抽象语法树生成及组成
1、抽象语法树的生成: 以function add(a, b){ return a+b}为例
A、分词:将源码分割成语法单元
B、语义分析: 分词结果之上分析这些语法单元之间的关系
组成:
首先add函数语法块是一个FunctionDeclaration对象,撤分为三块:
1、函数名称即ID :add
2、两个params : [a,b]
3、一个body块: 即 { return a + b}
ID 无法再拆,params 拆为 两个Identifier组成的数组,便无法拆除,body拆除:
body为一个BlockStatement对象: {return a+b},其中包含一个ReturnStatement对象: return a+b,继续打开包含一个BinaryExpression对象: a+b,由三部分组成left(a),operator(+),right(b),body无法再拆。及由这写拆分类内容组成抽象语法树。
pragram拆分图:
AST直观图:
recastAST结构:
三、AST涉及范围
1、AST in TS 和 ESlint
TS在大型js运用程序中,对开发者理解当前应用,程序运行稳定性及维护,和迭代项目的源码越来越重要。简单介绍一下关于TS中编译器与AST。
以 var a = 1为例,在ts编译器中parser阶段输出AST:
在内部,ts编译器将使用Parser生成AST来提供编译程序时的类型检测,我们也可以使用AST在TS之上开发代码美化工具、代码格式化工具、代码分析工具等。eslint原理类似,只是不同的parser工具转换成AST树形结构可能不同。 ESLint是一个用来检查和报告JavaScript编写规范的插件化工具,eslint 检测基于AST除了这些内置规则外,eslintAPI中,我们可利用源代码生成的AST,开发自定义插件和自定义规则。
本地自定义eslint规则:
2、AST in Common.js
Common.js 是模块化标准,node.js实现了这一标准,node.js
采用了模块规范。其模块化原理: 将js代码字符串解析为AST树,然后遍历AST树分析出require的依赖项,使用我们自己写得require函数来加载依赖项即可。具体可参考browserify转换模块,装载模块。
3、 AST in 打包工具(webpack)
Webpack是一个JavaScript生态的打包工具,其打出bundle结构是一个IIFE(立即执行函数),结构如下
(function(module){})([function(){},function(){}]);
Webpack 打包流程也需要AST支持,借助acron库解析源码,生成AST,提取相关模块依赖关系。其中支持treeShaking,使打包输出中去除没有引用的模块,有效减少包体积。
4、AST in others
AST可支持其他相关库:
a、Codemod(自动化修改代码库):如vscode自动保存格式化代码。
b、roollup等打包工具
c、JSX等转换为原生js等
d、Babel,js垫片等
四、AST recast 自定义实现箭头函数npm包实践
安装recast (npm i recast --dev-save)
具体代码如下:
五、AST猜想,与性能极致最求
1、极致性能追求,二进制AST
在大型js应用中,解析AST所需时间成为了应用性能瓶颈。二进制AST设想通过WebAssembly AST 所用的一些策略来提高解析 JavaScript AST 的性能。
二进制AST设想为js引入一种新的网络传输格式,该格式提供了AST的二进制编码,以此提升js性能。当前流行webpack rooloup等构建工具,及ts、babel这样的编译器可直接输出二进制AST,因此和这种提案十分吻合。
方案可解决问题:
a、在需要的地方无法获取信息,如变量提升,内置方法
b、需要对每个js文件进行欲解析
c、js 语法将表达式编译为何种类型的字符级歧义
完成提案:解析过程将比创建完整的AST所需时间减少70%-90%。
相关链接:
https://github.com/xitu/gold-miner/blob/master/TODO/binary-ast-newsletter-1.md