babe
l编译的第一步是把源码parse
成抽象语法树AST
(Abstract Syntax Tree
),后续对这个AST
进行转换,且后续的整个编译流程都是围绕AST
来进行的,我们先来熟悉下AST
。
常见的 AST 节点
AST是对源码的抽象,字面量
、标识符
、表达式
、语句
、模块语法
、class语法
都有各自的AST
。
Literal(字面量)
literal
是字面量的意思,比如let name = 'chuze'
中,'chuze'
就是一个字符串字面量StringLiteral
,相应的还有数字字面量NumberLiteral,布尔字面量BooleanLiteral,正则表达式字面量RegExpLiteral等。
下面是一些字面量对应的Literal节点:
字面量 | literal节点 |
---|---|
"chuze" | StringLiteral |
'chuze' | TemplateLiteral |
123 | NumberLiteral |
/^[a-z]+/ | RegExpLiteral |
true | BooleanLiteral |
1.234n | BigintLiteral |
null | NullLiteral |
代码中的字面量很多,babel
就是通过xxxLiteral
来抽象这部分内容。
Identifier(标识符)
Identifier
是标识符的意思,变量名
、属性名
、参数名
等各种声明和引用的名字,都是Identifier
。
下面分析下下面的代码中有多少Identifier
(标识符)
const name = 'chuze';
function say(name) {
console.log(name);
}
const obj = {
name: 'aaa',
};
Statement(语句)
statement
是语句,可以是独立执行的单位,比如break
、continue
、debugger
、return
、if语句
、while语句
、for语句
、声明语句
、表达式语句
等。我们写的每一条可以独立执行的代码都是语句。
语句末尾一般都会加一个分号分割,或者用换行分割。
下面这些我们经常写的代码,每一行都是一个Statement
:
break;
continue;
return;
debugger;
throw Error();
{}
try {} catch(e) {} finally{}
for (let key in obj) {}
for (let i = 0;i < 10;i ++) {}
while (true) {}
do {} while (true)
switch (v){case 1: break;default:;}
label: console.log();
with (a){}
对应的语句类型如下:
BreakStatement
ContinueStatement
ReturnStatement
DebuggerStatement
ThrowStatement
BlockStatement
TryStatement
ForInStatement
ForStatement
WhileStatement
DoWhileStatement
SwitchStatment
LabeledStatement
WithStatement
语句是代码执行的最小单位,代码是由语句
(Statement
)构成的。
Declaration(声明语句)
声明语句时一种特殊的语句,他执行的逻辑是在作用域内声明一个变量、函数、class、import、export等。
比如下面的这些语句都是声明语句:
const a = 1;
function b() {}
class C{}
import a fromm 'a';
export default e = 1;
export {e}
export * from 'e'
对应的节点有如下几种:
VariableDeclaration
FunctionDeclaration
ClassDeclaration
ImportDeclaration
ExportDeclaration
ExportNamedDeclaration
ExportAllDeclaration
声明语句用于定义变量,也是代码中的一个基础组成部分。
Expression (表达式)
表达式特点是执行完之后有返回值,这是和语句(statement)的区别。
下面是一些常见的表达式:
[1,2,3]
a = 1
1 + 2;
-1;
function(){};
() => {};
class{};
a;
this;
super;
a::b;
对应的AST如下:
为什么这里Identifier和super也是表达式?
因为Indentifier、super有返回值,符合表达式的特点,所以也是expression。
我们判断AST节点是不是某种类型要看它是不是符合该种类型的特点,比如语句的特点是能够单独执行,表达式的特点是有返回值。
有的表达式可以单独执行,符合语句的特点,所以也是语句,比如赋值表达式、数组表达式等:
a = 1;
[1,2,3];
但是有些表达式不能单独执行,需要和其他类型的节点组合在一起构成语句。
比如匿名函数表达式和匿名class表达式单独执行会报错:
function() {};
class{}
这种需要和其他部分一起构成一条语句,比如组成赋值语句:
a = function() {};
b = class
这条赋值语句对应的AST是这样的:
赋值语句的AST节点
AssignmentExpression
包裹了一层ExpressionStatemen
节点,代表这个表达式是被当成语句执行的。
Class
class的语法也有专门的AST节点来表示。
整个class
的内容是ClassBody
,属性是ClassProperty
, 方法是ClassMethod
(通过kind
属性来区分是constructor
还是method
)。
比如下面的代码:
class Man extends Person {
name = "dahua";
constructor() {}
eat(){}
}
对应的AST是这样的:
class
是es next
的语法,babel
中有专门的AST
来表示它的内容。
Modules
es module
是语法级别的模块规范,所以也有专门的AST
节点。
Import
import
有三种语法:
named import
:
import {c, d} from 'c';
default import
:
import a from 'a';
namespaced import
:
import * as b from 'b';
这三种语法都对应ImportDeclaration
节点,但是specifiers属性不同,分别对应ImportSpicifier
,ImportDefaultSpecifier
,ImportNamespaceSpcifier
。
图中黄框标出的就是
specifier
部分,可以直观的看出整体结构相同,只是specifier
部分不同,所以import
语法的AST的结构是ImportDeclaration
包含各种importspecifier
。
Export
export
也有三种语法:
named export:
export {b, d};
default export:
export default a;
all export
;
export * from 'c';
分别对应ExportNamedDeclaration
、ExportDefaultDeclaration
、ExportAllDeclaration
的AST
。
比如这三种export
:
export { b, d};
export default a;
export * from 'c';
对应的AST节点为:
Program & Directive
program
是代表整个程序的节点,它有body
属性代表程序体,存放statement
数组,就是具体执行的语句的集合。还有directives
属性,存放Directive
节点,比如use strict
这种指令会使用Directive
节点表示。
Program
是包裹具体执行语句的节点,而Directive
则是代码中的指令部分。
File & Comment
babel
的AST
最外层节点是File
,它有program
、comments
、tokens
等属性,分别存放Program
程序体、注释、token等,是最外层节点。
注释分为块注释和行内注释,对应CommentBlock
和CommentLine
节点。
上面几种就是常见的一些AST
节点类型,babel
就是通过这些节点来抽象源码中不同的部分。