+
95
-

回答

了解AST之前,我们先来简单陈述一下JavaScript引擎的工作原理:



从上图中我们可以看到,JavaScript引擎做的第一件事情就是把JavaScript代码编译成抽象语法树,于是就有了本文对AST抽象语法树的浅析.

一、什么是AST抽象语法树

我们都知道,在传统的编译语言的流程中,程序的一段源代码在执行之前会经历三个步骤,统称为"编译":

1、分词/词法分析

这个过程会将由字符组成的字符串分解成有意义的代码块,这些代码块统称为词法单元(token).

举个例子: let a = 1, 这段程序通常会被分解成为下面这些词法单元: let 、a、=、1、 ;空格是否被当成此法单元,取决于空格在这门语言中的意义。

2、解析/语法分析

这个过程是将词法单元流转换成一个由元素嵌套所组成的代表了程序语法结构的树,这个树被称为"抽象语法树"(abstract syntax code,AST)
代码生成

3、将AST转换成可执行代码的过程被称为代码生成.

抽象语法树(abstract syntax code,AST)是源代码的抽象语法结构的树状表示,树上的每个节点都表示源代码中的一种结构,之所以说是抽象的,抽象表示把js代码进行了结构化的转化,转化为一种数据结构。这种数据结构其实就是一个大的json对象,json我们都熟悉,他就像一颗枝繁叶茂的树。有树根,有树干,有树枝,有树叶,无论多小多大,都是一棵完整的树。

简单理解,就是把我们写的代码按照一定的规则转换成一种树形结构。

二、AST的用途

AST的作用不仅仅是用来在JavaScript引擎的编译上,我们在实际的开发过程中也是经常使用的,比如我们常用的babel插件将 ES6转化成ES5、使用 UglifyJS来压缩代码 、css预处理器、开发WebPack插件、Vue-cli前端自动化工具等等,这些底层原理都是基于AST来实现的,AST能力十分强大, 能够帮助开发者理解JavaScript这门语言的精髓。

三、AST的结构

我们先来看一组简单的AST树状结构:

比如有下面的代码

var name='bfw';

假如有以上代码,在词法分析阶段,会先对整个代码进行扫描,生成tokens流,扫描过程如下:

我们会通过条件判断语句判断这个字符是 字母, "/" , "数字" , 空格 , "(" , ")" , ";" 等等。

如果是字母会继续往下看如果还是字母或者数字,会继续这一过程直到不是为止,这个时候发现找到的这个字符串是一个 "var", 是一个Keyword,并且下一个字符是一个 "空格", 就会生成{ "type" : "Keyword" , "value" : "var" }放入数组中。

它继续向下找发现了一个字母 'name'(因为找到的上一个值是 "var" 这个时候如果它发现下一个字符不是字母可能直接就会报错返回)并且后面是空格,生成{ "type" : "Identifier" , "value" : "name" }放到数组中。
发现了一个 "=", 生成了{ "type" : "Punctuator" , "value" : "=" }放到了数组中。

发现了'bfw',生成了{ "type" : "String" , "value" : "bfw" }放到了数组中。

解析如下:

{
"type": "Program",
"body": [
{
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "name"
},
"init": {
"type": "Literal",
"value": "bfw",
"raw": "'bfw'"
}
}
],
"kind": "var"
}
],
"sourceType": "script"
}

token格式就是

[
{
"type": "Keyword",
"value": "var"
},
{
"type": "Identifier",
"value": "name"
},
{
"type": "Punctuator",
"value": "="
},
{
"type": "String",
"value": "'bfw'"
},
{
"type": "Punctuator",
"value": ";"
}
]

可以使用这个在线ast生成工具:https://esprima.org/demo/parse.html#

还可以通过esprima来生成,先安装esprima 

npm i esprima --save

代码来生成

const esprima = require('esprima');
let code = 'var name = "bfw" ';
const ast = esprima.parseScript(code);
console.log(ast);

那么我们如果想要修改这个name的变量名称和值,怎么办?

通过ast轻松解决,代码如下:

const esprima = require('esprima');
const estraverse = require('estraverse');
let code = 'var name = "bfw" ';
const ast = esprima.parseScript(code);
estraverse.traverse(ast, {
enter: function (node) {
node.name = 'age';
node.value = "11岁";
}
});
console.log(ast);

generator将更新后的AST转化成代码

这里我们使用escodegen去生成, 安装相关依赖 npm i escodegen --save

整体代码结构如下:

const esprima = require('esprima');
const estraverse = require('estraverse');
const escodegen = require('escodegen');
let code = 'var name = "bfw" ';
const ast = esprima.parseScript(code);
estraverse.traverse(ast, {
enter: function (node) {
node.name = 'age';
node.value = "11岁";
}
});
const transformCode = escodegen.generate(ast);
console.log(transformCode);

参考文章https://zhuanlan.zhihu.com/p/266697614

网友回复

我知道答案,我要回答