AST 抽象语法树简介

AST(Abstract Syntax Tree)是源代码的抽象语法结构树状表现形式,Webpack、ESLint、JSX、TypeScript 的编译和模块化规则之间的转化都是通过 AST 来实现对代码的检查、分析以及编译等操作。

JavaScript 语法的 AST 语法树

JavaScript 中想要使用 AST 进行开发,要知道抽象成语法树之后的结构是什么,里面的字段名称都代表什么含义以及遍历的规则,可以通过 http://esprima.org/demo/parse... 来实现 JavaScript 语法的在线转换。

通过在线编译工具,可以将 function fn(a, b) {} 编译为下面的结构。

{

"type": "Program",

"body": [

{

"type": "FunctionDeclaration",

"id": {

"type": "Identifier",

"name": "fn"

},

"params": [

{

"type": "Identifier",

"name": "a"

},

{

"type": "Identifier",

"name": "b"

}

],

"body": {

"type": "BlockStatement",

"body": []

},

"generator": false,

"expression": false,

"async": false

}

],

"sourceType": "script"

}

将 JavaScript 语法编译成抽象语法树后,需要对它进行遍历、修该并重新编译,遍历树结构的过程为 “先序深度优先”。

esprima、estraverse 和 escodegen

esprima、estraverse 和 escodegen 模块是操作 AST 的三个重要模块,也是实现 babel 的核心依赖,下面是分别介绍三个模块的作用。

1、esprima 将 JS 转换成 AST

esprima 模块的用法如下:

// 文件:esprima-test.js

const esprima = require("esprima");

let code = "function fn() {}";

// 生成语法树

let tree = esprima.parseScript(code);

console.log(tree);

// Script {

// type: 'Program',

// body:

// [ FunctionDeclaration {

// type: 'FunctionDeclaration',

// id: [Identifier],

// params: [],

// body: [BlockStatement],

// generator: false,

// expression: false,

// async: false } ],

// sourceType: 'script' }

通过上面的案例可以看出,通过 esprima 模块的 parseScript 方法将 JS 代码块转换成语法树,代码块需要转换成字符串,也可以通过 parseModule 方法转换一个模块。

2、estraverse 遍历和修改 AST

查看遍历过程:

// 文件:estraverse-test.js

const esprima = require("esprima");

const estraverse = require("estraverse");

let code = "function fn() {}";

// 遍历语法树

estraverse.traverse(esprima.parseScript(code), {

enter(node) {

console.log("enter", node.type);

},

leave() {

console.log("leave", node.type);

}

});

// enter Program

// enter FunctionDeclaration

// enter Identifier

// leave Identifier

// enter BlockStatement

// leave BlockStatement

// leave FunctionDeclaration

// leave Program

上面代码通过 estraverse 模块的 traverse 方法将 esprima 模块转换的 AST 进行了遍历,并打印了所有的 type 属性并打印,每含有一个 type 属性的对象被叫做一个节点,修改是获取对应的类型并修改该节点中的属性即可。

其实深度遍历 AST 就是在遍历每一层的 type 属性,所以遍历会分为两个阶段,进入阶段和离开阶段,在 estraverse 的 traverse 方法中分别用参数指定的 entry 和 leave 两个函数监听,但是我们一般只使用 entry。

3、escodegen 将 AST 转换成 JS

下面的案例是一个段 JS 代码块被转换成 AST,并将遍历、修改后的 AST 重新转换成 JS 的全过程。

// 文件:escodegen-test.js

const esprima = require("esprima");

const estraverse = require("estraverse");

const escodegen = require("escodegen");

let code = "function fn() {}";

// 生成语法树

let tree = esprima.parseScript(code);

// 遍历语法树

estraverse.traverse(tree, {

enter(node) {

// 修改函数名

if (node.type === "FunctionDeclaration") {

node.id.name = "ast";

}

}

});

// 编译语法树

let result = escodegen.generate(tree);

console.log(result);

// function ast() {

// }

在遍历 AST 的过程中 params 值为数组,没有 type 属性。

实现 Babel 语法转换插件

实现语法转换插件需要借助 babel-core 和 babel-types 两个模块,其实这两个模块就是依赖 esprima、estraverse 和 escodegen 的。

使用这两个模块需要安装,命令如下:

npm install babel-core babel-types

1、plugin-transform-arrow-functions

plugin-transform-arrow-functions 是 Babel 家族成员之一,用于将箭头函数转换 ES5 语法的函数表达式。

// 文件:plugin-transform-arrow-functions.js

const babel = require("babel-core");

const types = require("babel-types");

// 箭头函数代码块

let sumCode = `

const sum = (a, b) => {

return a + b;

}`;

let minusCode = `const minus = (a, b) => a - b;`;

// 转化 ES5 插件

let ArrowPlugin = {

// 访问者(访问者模式)

visitor: {

// path 是树的路径

ArrowFunctionExpression(path) {

// 获取树节点

let node = path.node;

// 获取参数和函数体

let params = node.params;

let body = node.body;

// 判断函数体是否是代码块,不是代码块则添加 return 和 {}

if (!types.isBlockStatement(body)) {

let returnStatement = types.returnStatement(body);

body = types.blockStatement([returnStatement]);

}

// 生成一个函数表达式树结构

let func = types.functionExpression(null, params, body, false, false);

// 用新的树结构替换掉旧的树结构

types.replaceWith(func);

}

}

};

// 生成转换后的代码块

let sumResult = babel.transform(sumCode, {

plugins: [ArrowPlugin]

});

let minusResult = babel.transform(minusCode, {

plugins: [ArrowPlugin]

});

console.log(sumResult.code);

console.log(minusResult.code);

// let sum = function (a, b) {

// return a + b;

// };

// let minus = function (a, b) {

// return a - b;

// };

我们主要使用 babel-core 的 transform 方法将 AST 转化成代码块,第一个参数为转换前的代码块(字符串),第二个参数为配置项,其中 plugins 值为数组,存储修改 babal-core 转换的 AST 的插件(对象),使用 transform 方法将旧的 AST 处理成新的代码块后,返回值为一个对象,对象的 code 属性为转换后的代码块(字符串)。

内部修改通过 babel-types 模块提供的方法实现,API 可以到 https://github.com/babel/babe... 中查看。

ArrowPlugin 就是传入 transform 方法的插件,必须含有 visitor 属性(固定),值同为对象,用于存储修改语法树的方法,方法名要严格按照 API,对应的方法会修改 AST 对应的节点。

在 types.functionExpression 方法中参数分别代表,函数名(匿名函数为 null)、函数参数(必填)、函数体(必填)、是否为 generator 函数(默认 false)、是否为 async 函数(默认 false),返回值为修改后的 AST,types.replaceWith 方法用于替换 AST,参数为新的 AST。

2、plugin-transform-classes

plugin-transform-classes 也是 Babel 家族中的成员之一,用于将 ES6 的 class 类转换成 ES5 的构造函数。

// 文件:plugin-transform-classes.js

const babel = require("babel-core");

const types = require("babel-types");

// 类

let code = `

class Person {

constructor(name) {

this.name = name;

}

getName () {

return this.name;

}

}`;

// 将类转化 ES5 构造函数插件

let ClassPlugin = {

visitor: {

ClassDeclaration(path) {

let node = path.node;

let classList = node.body.body;

// 将取到的类名转换成标识符 { type: 'Identifier', name: 'Person' }

let className = types.identifier(node.id.name);

let body = types.blockStatement([]);

let func = types.functionDeclaration(className, [], body, false, false);

path.replaceWith(func);

// 用于存储多个原型方法

let es5Func = [];

// 获取 class 中的代码体

classList.forEach((item, index) => {

// 函数的代码体

let body = classList[index].body;

// 获取参数

let params = item.params.length ? item.params.map(val => val.name) : [];

// 转化参数为标识符

params = types.identifier(params);

// 判断是否是 constructor,如果构造函数那就生成新的函数替换

if (item.kind === "constructor") {

// 生成一个构造函数树结构

func = types.functionDeclaration(className, [params], body, false, false);

} else {

// 其他情况是原型方法

let proto = types.memberExpression(className, types.identifier("prototype"));

// 左侧层层定义标识符 Person.prototype.getName

let left = types.memberExpression(proto, types.identifier(item.key.name));

// 右侧定义匿名函数

let right = types.functionExpression(null, [params], body, false, false);

// 将左侧和右侧进行合并并存入数组

es5Func.push(types.assignmentExpression("=", left, right));

}

});

// 如果没有原型方法,直接替换

if (es5Func.length === 0) {

path.replaceWith(func);

} else {

es5Func.push(func);

// 替换 n 个节点

path.replaceWithMultiple(es5Func);

}

}

}

};

// 生成转换后的代码块

result = babel.transform(code, {

plugins: [ClassPlugin]

});

console.log(result.code);

// Person.prototype.getName = function () {

// return this.name;

// }

// function Person(name) {

// this.name = name;

// }

上面这个插件的实现要比 plugin-transform-arrow-functions 复杂一些,归根结底还是将要互相转换的 ES6 和 ES5 语法树做对比,找到他们的不同,并使用 babel-types 提供的 API 对语法树对应的节点属性进行修改并替换语法树,值得注意的是 path.replaceWithMultiple 与 path.replaceWith 不同,参数为一个数组,数组支持多个语法树结构,可根据具体修改语法树的场景选择使用,也可根据不同情况使用不同的替换方法。

总结

通过本节我们了解了什么是 AST 抽象语法树、抽象语法树在 JavaScript 中的体现以及在 NodeJS 中用于生成、遍历和修改 AST 抽象语法树的核心依赖,并通过使用 babel-core 和 babel-types 两个模块简易模拟了 ES6 新特性转换为 ES5 语法的过程,希望可以为后面自己实现一些编译插件提供了思路。

java抽象语法树(ast),AST 抽象语法树相关推荐

  1. ast抽象语法树_新抽象语法树(AST)给 PHP7 带来的变化

    本文大部分内容参照 AST 的 RFC 文档而成:https://wiki.php.net/rfc/abstract_syntax_tree,为了易于理解从源文档中节选部分进行介绍. 我的官方群点击此 ...

  2. java抽象语法树_抽象语法树AST的全面解析(一)

    Javac编译概述 将.java源文件编译成.class文件,这一步大致可以分为3个过程: 1.把所有的源文件解析成语法树,输入到编译器的符号表: 2.注解处理器的注解处理过程: 3.分析语法树并生成 ...

  3. hive遍历_Hive解析流程-抽象语法树生成

    HiveQL解析流程: Hive根据Antlr定义的词法.语法规则完成词法.语法分析将HQL解析为AST Tree: 遍历AST Tree,抽象出查询的基本组成单元Query Block: 遍历Que ...

  4. hive查询where join_Hive解析流程-抽象语法树生成

    HiveQL解析流程: Hive根据Antlr定义的词法.语法规则完成词法.语法分析将HQL解析为AST Tree: 遍历AST Tree,抽象出查询的基本组成单元Query Block: 遍历Que ...

  5. 抽象语法树在 JavaScript 中的应用

    抽象语法树是什么 在计算机科学中,抽象语法树(abstract syntax tree 或者缩写为 AST),或者语法树(syntax tree),是源代码的抽象语法结构的树状表现形式,这里特指编程语 ...

  6. php ast 抽象语法树,AST抽象语法树的基本思想

    AST抽象语法树的基本思想 前言 AST概述 AST结构 AST解析 转换 生成 前言 在阅读java ORM框架spring data jpa的源码时,发现Hibernate(spring data ...

  7. 【转】抽象语法树简介(AST)

    引用地址:http://blog.chinaunix.net/uid-26750235-id-3139100.html 抽象语法树简介 (一)简介 抽象语法树(abstract syntax code ...

  8. AST抽象语法树的基本思想

    AST抽象语法树的基本思想 前言 AST概述 AST结构 AST解析 转换 生成 前言 在阅读java ORM框架spring data jpa的源码时,发现Hibernate(spring data ...

  9. AST(抽象语法树)超详细

    自己研究的东西会用到AST,就自己通过查阅资料,整理一下. 本文目录 第一部分:AST的作用 第二部分:AST的流程 第三部分: Eclipse AST的获取与访问 第一部分:AST的作用 首先来一个 ...

  10. 应用ast抽象语法树修改js函数

    原理:AST抽象语法树 目标:在每一个函数里面插入一个console.log()把函数传入的全部参数输出出来 关于:本文章是在基于我的个人理解且怕忘记知识所记录下来的给自己看并且分享自己的一个心得,文 ...

最新文章

  1. 零基础入门学习Python(25)-集合·
  2. python3运行报错:TypeError: Object of type ‘type‘ is not JSON serializable解决方法(详细)
  3. 【Linux】rpm常用命令及rpm参数介绍
  4. YYCache深入学习
  5. 项目进度管理:规划项目进度管理
  6. 服务器硬盘raid5扩容,超实用,物理服务器RAID扩容详解
  7. python编辑配置_Python环境安装及编辑器配置(一)
  8. STM32复位源判断
  9. eclipse安装aptana插件对html、js、jq、css等的提示
  10. python读取超大csv
  11. UISearchController使用方法及注意事项
  12. Opengl1.1绘图之GL_COLOR_LOGIC_OP
  13. 关于adb no serial number的解决方案
  14. 纠错码较副本优势与minio纠错码配置
  15. 光伏发电系统及其MPPT控制
  16. 演进:如何用练习快速提升技术
  17. Labview2018学习之三:LabVIEW编程
  18. 数据预处理Part8——数据共线性
  19. 我用前世的五百次回眸换今生与你一次擦肩而过
  20. 集成融云RongCloud视频通话功能PC端

热门文章

  1. java security / SSL / TLS / md5 / sha / base64 / rsa / des / aes / 3des
  2. 如何使用Application Loder 上传app
  3. [勇者闯LeetCode] 169. Majority Element
  4. 三种前端实现VR全景看房的方案!说不定哪天就用得上!
  5. MutatorMath 与响应式字体设计
  6. 接口报错401 状态码 Unauthorized
  7. 关系从句逻辑【Relational clausal logic】
  8. 金行健:小米汽车经理级工程师薪酬曝光,年薪60万配100万期权
  9. oracle在线的表名修改,oracle中如何修改表名
  10. netty跨域问题解决