当前位置: 首页 > news >正文

ast语法树初探

前端开发中,使用了很多工具,譬如webpack、eslint来提升研发效率,但我们并不知道这些工具的实现原理。基于这些工具的核心都是抽象语法树,那我们就从抽象语法树开始理解底层原理的新世界吧。

一、抽象语法树是什么

顾名思义,首先可以确定的是,这是一颗跟语法相关的树。

先上一盘硬菜,维基百科定义如下:

In computer science, an abstract syntax tree (AST), or just syntax tree, is a tree representation of the abstract syntactic structure of source code written in a programming language.

也就是说,抽象语法树,是通过编程语言编写的代码的抽象语法结构。

用通俗的话讲,我们所使用的编程语言是一门对人类友好的语言,但是对于程序分析来讲,并不友好。因此,需要将编程语言转译成对程序分析友好的语言。

太干了,来点配菜。

我们结合编译过程,来说明抽象语法树的作用。

如下图所示,一般的编译过程分为六个过程。

 那么在语法分析阶段,就是要将词法分析阶段得到的分词结果整合成一棵语法树。简单举个例子:

代码:

1

2

3

4

function foo(a) {

    let b = a + 3;

    return b;

}

将这个函数转成抽象语法树,其核心部分如下图所示:

blockstatement 块级作用域。

VariableDecalaration 变量声明

VariableDecalarator 变量声明器

Returnstatement 返回语句

 在转成语法树之后,就可以对语句进行语法检查或进行修改了。

二、 语法树的应用

前面提到,可以通过对语法树分析来进行语法检查,有没有很熟悉? 有没有想到我们日常写代码过程中用到的eslint 插件、括号高亮插件等。 没有错,他们就是通过对ast 抽象语法树进行分析,来达成语法检查和高亮目的。

话不多说,看图。

 这是一个ast 在我们开发流程中的一个应用总结。

编写代码:我们用 vue的 模板语法去定义一些功能,而这些模板语法最后都要通过对 ast 进行分析最终转为原生 html 和原生 js。

babel 、webpack 和 ast 的关系:

开发过程中,我们使用了大量处于提案阶段的 es6 的 语法,譬如装饰器、箭头函数、模板字符串等,而这些语法,实际上浏览器本身并不支持。因此,需要将其转成 浏览器支持的 es5 代码。这个es 降级的过程就是通过 babel 完成的,而 babel 的核心也就是通过对 ast 进行更改,从而实现 es 语法降级。

webpack 作为模块化开发的主力军,在打包阶段,也是通过对语法树进行分析,最终打包成一个文件上传到服务器上去的。

浏览器执行代码和 ast 的关系:

如图中所示,浏览器执行js 代码的过程和我们上面所阐述的编译的六个阶段是极其一致的。

综上所述,语法树和我们开发的整个过程都是息息相关的。

认识到了语法树在开发过程中的重要性。 接下来,我们通过 babel 的核心库 来简单模拟 es6 转 es5 的过程。

转译过程大致可以分为三步:

  1. 生成语法树
  2. 语法树遍历,更改
  3. 再次遍历语法树,生成新的代码。

通过库 esprima 生成语法树, 通过库 estraverse 遍历语法树并进行语法更改, 通过库 escodegen 遍历语法树,并生成新的代码。 

三、 语法树的生成

语法树生成使用的库是 esprima。

我们先通过一个简单的es6 例子来说明。

es6 通过 let 关键字进行变量声明,从而实现块级作用域。那么转成 es 5 的话只能是 ‘var’。即,代码 ”let a = 15; “ 转变为 ”var a = 15;“。

首先,导入所使用的几个库(库的地址在本文末尾。)并定义字符串。

1

2

3

4

const prima = require('esprima');

const codegen = require('escodegen');

const traverse = require('estraverse');

var  code2 = 'let a = 2';

我们知道,如果要转成语法树,首先要进行词法分析,通过调用esprima 下的 tokenize函数(console.log(prima.tokenize(code2)))可以查看其生成的token。也可以通过可视化工具esprima 可视化查看词法分析结果。

从图中可以看出,esprima 对字符串中的每一个此进行解析,并赋予相应的 type。

在词法解析结束后,再进行语法解析形成 ast 树,可以通过命令行

1

console.log(prima.parseScript(code2));

查看其 ast 的结果,也可以通过 ast 可视化工具 查看其结果。这里展示通过 ast 可视化工具的结果:

 

es6 语法树:     

es5语法树:

 对比这两棵语法树,我们可以看出,其唯一的区别在于,变量声明下,其 kind 节点的值。 那么,接下来,在遍历语法树的时候,我们只要对 node 下的 kind 节点进行变更就可以完成语法转变了。

四、语法树的遍历和更改

我们可以通过  estraverse下的 traverse 函数进行语法树遍历并进行相应更改。

1

2

3

4

5

6

7

8

9

const ast2 = prima.parseScript(code2);

traverse.traverse(ast2, {

    enter(node) {

        if(node.kind === 'let') {

            node.kind = 'var';

        }

    },

});

五、生成 es5 代码

我们通过 escodegen 下的 generate 函数生成 新的es5 代码:

1

2

3

let code2_es5 = codegen.generate(ast2);

console.log(code2_es5);

//'var a = 2;'

写到这里,基本上对 ast 的 what、why、how 进行了一个基本的介绍。 但美中不足的是,上面对于语法树的解析只是利用库函数进行的。接下来,给大家介绍一些 ast 相关的小知识,并尝试带大家,读一读这些函数的源代码。

六、AST 小知识

js 解析器有哪些?我们所使用的工具内核是什么?

常用的 js 解析器有:

esprima: https://github.com/jquery/esprima

acorn:https://github.com/acornjs/acorn

acorn 的诞生晚于 esprima,期因是 esprima 转换速度太慢。

而 babel 目前所用的解析器 fork 自 acorn。webpack 的核心 parser 也是 acorn。而 eslint 作为一个可配置的代码规范检查工具,可以任意选择定义解析器来使用。

而不管是那个解析器,他们解析得到的 ast 树都符合ast 规则:https://github.com/estree/estree

规则起源:

在v8引擎之前,最早js引擎是SpiderMonkey,第一个版本由js作者Brendan Eich设计,后交给Mozilla组织维护。js引擎在执行js文件时,都会先将js代码转换成抽象语法树(AST)。有一天,一位Mozilla工程师在FireFox中公开了这个将代码转成AST的解析器Api,也就是Parser_API,后来被人整理到github项目estree,慢慢的成了业界的规范。

七、 源码解读

待填坑

相关文章:

  • idea 如何不重启服务进行修改-(热部署)