前言
Babel為當前最流行的程式碼JavaScript編譯器了,其使用的JavaScript解析器為babel-parser,最初是從Acorn
專案fork
出來的。Acorn 非常快,易於使用,並且針對非標準特性(以及那些未來的標準特性) 設計了一個基於外掛的架構。本文主要介紹esprima
解析生成的抽象語法樹節點,esprima
的實現也是基於Acorn的。
解析器 Parser
JavaScript Parser 是把js原始碼轉化為抽象語法樹(AST)的解析器。這個步驟分為兩個階段:詞法分析(Lexical Analysis) 和 語法分析(Syntactic Analysis)。
常用的JavaScript Parser:
詞法分析
詞法分析階段把字串形式的程式碼轉換為 令牌(tokens)流。你可以把令牌看作是一個扁平的語法片段陣列。
n * n;
複製程式碼
例如上面n*n
的詞法分析得到結果如下:
[
{ type: { ... }, value: "n", start: 0, end: 1, loc: { ... } },
{ type: { ... }, value: "*", start: 2, end: 3, loc: { ... } },
{ type: { ... }, value: "n", start: 4, end: 5, loc: { ... } },
]
複製程式碼
每一個 type 有一組屬性來描述該令牌:
{
type: {
label: 'name',
keyword: undefined,
beforeExpr: false,
startsExpr: true,
rightAssociative: false,
isLoop: false,
isAssign: false,
prefix: false,
postfix: false,
binop: null,
updateContext: null
},
...
}
複製程式碼
和 AST 節點一樣它們也有 start,end,loc 屬性。
語法分析
語法分析就是根據詞法分析的結果,也就是令牌tokens,將其轉換成AST。
function square(n) {
return n * n;
}
複製程式碼
如上面程式碼,生成的AST結構如下:
{
type: "FunctionDeclaration",
id: {
type: "Identifier",
name: "square"
},
params: [{
type: "Identifier",
name: "n"
}],
body: {
type: "BlockStatement",
body: [{
type: "ReturnStatement",
argument: {
type: "BinaryExpression",
operator: "*",
left: {
type: "Identifier",
name: "n"
},
right: {
type: "Identifier",
name: "n"
}
}
}]
}
}
複製程式碼
下文將對AST各個型別節點做解釋。更多AST生成,入口如下:
結合視覺化工具,舉個例子
如下程式碼:
var a = 42;
var b = 5;
function addA(d) {
return a + d;
}
var c = addA(2) + b;
複製程式碼
第一步詞法分析之後長成如下圖所示:
語法分析,生產抽象語法樹,生成的抽象語法樹如下圖所示
Base
Node
所有節點型別都實現以下介面:
interface Node {
type: string;
range?: [number, number];
loc?: SourceLocation;
}
複製程式碼
該type欄位是表示AST變體型別的字串。該loc欄位表示節點的源位置資訊。如果解析器沒有生成有關節點源位置的資訊,則該欄位為null;否則它是一個物件,包括一個起始位置(被解析的源區域的第一個字元的位置)和一個結束位置.
interface SourceLocation {
start: Position;
end: Position;
source?: string | null;
}
複製程式碼
每個Position物件由一個line數字(1索引)和一個column數字(0索引)組成:
interface Position {
line: uint32 >= 1;
column: uint32 >= 0;
}
複製程式碼
Programs
interface Program <: Node {
type: "Program";
sourceType: 'script' | 'module';
body: StatementListItem[] | ModuleItem[];
}
複製程式碼
表示一個完整的原始碼樹。
Scripts and Modules
原始碼數的來源包括兩種,一種是script指令碼,一種是modules模組
當為script時,body為StatementListItem
。
當為modules時,body為ModuleItem
。
型別StatementListItem
和ModuleItem
型別如下。
type StatementListItem = Declaration | Statement;
type ModuleItem = ImportDeclaration | ExportDeclaration | StatementListItem;
複製程式碼
ImportDeclaration
import語法,匯入模組
type ImportDeclaration {
type: 'ImportDeclaration';
specifiers: ImportSpecifier[];
source: Literal;
}
複製程式碼
ImportSpecifier
型別如下:
interface ImportSpecifier {
type: 'ImportSpecifier' | 'ImportDefaultSpecifier' | 'ImportNamespaceSpecifier';
local: Identifier;
imported?: Identifier;
}
複製程式碼
ImportSpecifier
語法如下:
import { foo } from './foo';
複製程式碼
ImportDefaultSpecifier
語法如下:
import foo from './foo';
複製程式碼
ImportNamespaceSpecifier
語法如下
import * as foo from './foo';
複製程式碼
ExportDeclaration
export型別如下
type ExportDeclaration = ExportAllDeclaration | ExportDefaultDeclaration | ExportNamedDeclaration;
複製程式碼
ExportAllDeclaration
從指定模組中匯出
interface ExportAllDeclaration {
type: 'ExportAllDeclaration';
source: Literal;
}
複製程式碼
語法如下:
export * from './foo';
複製程式碼
ExportDefaultDeclaration
匯出預設模組
interface ExportDefaultDeclaration {
type: 'ExportDefaultDeclaration';
declaration: Identifier | BindingPattern | ClassDeclaration | Expression | FunctionDeclaration;
}
複製程式碼
語法如下:
export default 'foo';
複製程式碼
ExportNamedDeclaration
匯出部分模組
interface ExportNamedDeclaration {
type: 'ExportNamedDeclaration';
declaration: ClassDeclaration | FunctionDeclaration | VariableDeclaration;
specifiers: ExportSpecifier[];
source: Literal;
}
複製程式碼
語法如下:
export const foo = 'foo';
複製程式碼
Declarations and Statements
declaration
,即宣告,型別如下:
type Declaration = VariableDeclaration | FunctionDeclaration | ClassDeclaration;
複製程式碼
statements
,即語句,型別如下:
type Statement = BlockStatement | BreakStatement | ContinueStatement |
DebuggerStatement | DoWhileStatement | EmptyStatement |
ExpressionStatement | ForStatement | ForInStatement |
ForOfStatement | FunctionDeclaration | IfStatement |
LabeledStatement | ReturnStatement | SwitchStatement |
ThrowStatement | TryStatement | VariableDeclaration |
WhileStatement | WithStatement;
複製程式碼
VariableDeclarator
變數宣告,kind 屬性表示是什麼型別的宣告,因為 ES6 引入了 const/let。
interface VariableDeclaration <: Declaration {
type: "VariableDeclaration";
declarations: [ VariableDeclarator ];
kind: "var" | "let" | "const";
}
複製程式碼
FunctionDeclaration
函式宣告(非函式表示式)
interface FunctionDeclaration {
type: 'FunctionDeclaration';
id: Identifier | null;
params: FunctionParameter[];
body: BlockStatement;
generator: boolean;
async: boolean;
expression: false;
}
複製程式碼
例如:
function foo() {}
function *bar() { yield "44"; }
async function noop() { await new Promise(function(resolve, reject) { resolve('55'); }) }
複製程式碼
ClassDeclaration
類宣告(非類表示式)
interface ClassDeclaration {
type: 'ClassDeclaration';
id: Identifier | null;
superClass: Identifier | null;
body: ClassBody;
}
複製程式碼
ClassBody
宣告如下:
interface ClassBody {
type: 'ClassBody';
body: MethodDefinition[];
}
複製程式碼
MethodDefinition
表示方法宣告;
interface MethodDefinition {
type: 'MethodDefinition';
key: Expression | null;
computed: boolean;
value: FunctionExpression | null;
kind: 'method' | 'constructor';
static: boolean;
}
複製程式碼
class foo {
constructor() {}
method() {}
};
複製程式碼
ContinueStatement
continue語句
interface ContinueStatement {
type: 'ContinueStatement';
label: Identifier | null;
}
複製程式碼
例如:
for (var i = 0; i < 10; i++) {
if (i === 0) {
continue;
}
}
複製程式碼
DebuggerStatement
debugger語句
interface DebuggerStatement {
type: 'DebuggerStatement';
}
複製程式碼
例如
while(true) {
debugger;
}
複製程式碼
DoWhileStatement
do-while語句
interface DoWhileStatement {
type: 'DoWhileStatement';
body: Statement;
test: Expression;
}
複製程式碼
test
表示while條件
例如:
var i = 0;
do {
i++;
} while(i = 2)
複製程式碼
EmptyStatement
空語句
interface EmptyStatement {
type: 'EmptyStatement';
}
複製程式碼
例如:
if(true);
var a = [];
for(i = 0; i < a.length; a[i++] = 0);
複製程式碼
ExpressionStatement
表示式語句,即,由單個表示式組成的語句。
interface ExpressionStatement {
type: 'ExpressionStatement';
expression: Expression;
directive?: string;
}
複製程式碼
當表示式語句表示一個指令(例如“use strict”)時,directive屬性將包含該指令字串。
例如:
(function(){});
複製程式碼
ForStatement
for語句
interface ForStatement {
type: 'ForStatement';
init: Expression | VariableDeclaration | null;
test: Expression | null;
update: Expression | null;
body: Statement;
}
複製程式碼
ForInStatement
for...in語句
interface ForInStatement {
type: 'ForInStatement';
left: Expression;
right: Expression;
body: Statement;
each: false;
}
複製程式碼
ForOfStatement
for...of語句
interface ForOfStatement {
type: 'ForOfStatement';
left: Expression;
right: Expression;
body: Statement;
}
複製程式碼
IfStatement
if 語句
interface IfStatement {
type: 'IfStatement';
test: Expression;
consequent: Statement;
alternate?: Statement;
}
複製程式碼
consequent
表示if命中後內容,alternate
表示else或者else if的內容。
LabeledStatement
label語句,多用於精確的使用巢狀迴圈中的continue和break。
interface LabeledStatement {
type: 'LabeledStatement';
label: Identifier;
body: Statement;
}
複製程式碼
如:
var num = 0;
outPoint:
for (var i = 0 ; i < 10 ; i++){
for (var j = 0 ; j < 10 ; j++){
if( i == 5 && j == 5 ){
break outPoint;
}
num++;
}
}
複製程式碼
ReturnStatement
return 語句
interface ReturnStatement {
type: 'ReturnStatement';
argument: Expression | null;
}
複製程式碼
SwitchStatement
Switch語句
interface SwitchStatement {
type: 'SwitchStatement';
discriminant: Expression;
cases: SwitchCase[];
}
複製程式碼
discriminant
表示switch的變數。
SwitchCase
型別如下
interface SwitchCase {
type: 'SwitchCase';
test: Expression | null;
consequent: Statement[];
}
複製程式碼
ThrowStatement
throw語句
interface ThrowStatement {
type: 'ThrowStatement';
argument: Expression;
}
複製程式碼
TryStatement
try...catch語句
interface TryStatement {
type: 'TryStatement';
block: BlockStatement;
handler: CatchClause | null;
finalizer: BlockStatement | null;
}
複製程式碼
handler
為catch處理宣告內容,finalizer
為finally內容。
CatchClaus
型別如下
interface CatchClause {
type: 'CatchClause';
param: Identifier | BindingPattern;
body: BlockStatement;
}
複製程式碼
例如:
try {
foo();
} catch (e) {
console.erroe(e);
} finally {
bar();
}
複製程式碼
WhileStatement
while語句
interface WhileStatement {
type: 'WhileStatement';
test: Expression;
body: Statement;
}
複製程式碼
test
為判定表示式
WithStatement
with語句(指定塊語句的作用域的作用域)
interface WithStatement {
type: 'WithStatement';
object: Expression;
body: Statement;
}
複製程式碼
如:
var a = {};
with(a) {
name = 'xiao.ming';
}
console.log(a); // {name: 'xiao.ming'}
複製程式碼
Expressions and Patterns
Expressions
可用型別如下:
type Expression = ThisExpression | Identifier | Literal |
ArrayExpression | ObjectExpression | FunctionExpression | ArrowFunctionExpression | ClassExpression |
TaggedTemplateExpression | MemberExpression | Super | MetaProperty |
NewExpression | CallExpression | UpdateExpression | AwaitExpression | UnaryExpression |
BinaryExpression | LogicalExpression | ConditionalExpression |
YieldExpression | AssignmentExpression | SequenceExpression;
複製程式碼
Patterns
可用有兩種型別,函式模式和物件模式如下:
type BindingPattern = ArrayPattern | ObjectPattern;
複製程式碼
ThisExpression
this
表示式
interface ThisExpression {
type: 'ThisExpression';
}
複製程式碼
Identifier
識別符號,就是我們寫 JS 時自定義的名稱,如變數名,函式名,屬性名,都歸為識別符號。相應的介面是這樣的:
interface Identifier {
type: 'Identifier';
name: string;
}
複製程式碼
Literal
字面量,這裡不是指 [] 或者 {} 這些,而是本身語義就代表了一個值的字面量,如 1,“hello”, true 這些,還有正規表示式(有一個擴充套件的 Node 來表示正規表示式),如 /\d?/。
interface Literal {
type: 'Literal';
value: boolean | number | string | RegExp | null;
raw: string;
regex?: { pattern: string, flags: string };
}
複製程式碼
例如:
var a = 1;
var b = 'b';
var c = false;
var d = /\d/;
複製程式碼
ArrayExpression
陣列表示式
interface ArrayExpression {
type: 'ArrayExpression';
elements: ArrayExpressionElement[];
}
複製程式碼
例:
[1, 2, 3, 4];
複製程式碼
ArrayExpressionElement
陣列表示式的節點,型別如下
type ArrayExpressionElement = Expression | SpreadElement;
複製程式碼
Expression包含所有表示式,SpreadElement為擴充套件運算子語法。
SpreadElement
擴充套件運算子
interface SpreadElement {
type: 'SpreadElement';
argument: Expression;
}
複製程式碼
如:
var a = [3, 4];
var b = [1, 2, ...a];
var c = {foo: 1};
var b = {bar: 2, ...c};
複製程式碼
ObjectExpression
物件表示式
interface ObjectExpression {
type: 'ObjectExpression';
properties: Property[];
}
複製程式碼
Property
代表為物件的屬性描述
型別如下
interface Property {
type: 'Property';
key: Expression;
computed: boolean;
value: Expression | null;
kind: 'get' | 'set' | 'init';
method: false;
shorthand: boolean;
}
複製程式碼
kind
用來表示是普通的初始化,或者是 get/set。
例如:
var obj = {
foo: 'foo',
bar: function() {},
noop() {}, // method 為 true
['computed']: 'computed' // computed 為 true
}
複製程式碼
FunctionExpression
函式表示式
interface FunctionExpression {
type: 'FunctionExpression';
id: Identifier | null;
params: FunctionParameter[];
body: BlockStatement;
generator: boolean;
async: boolean;
expression: boolean;
}
複製程式碼
例如:
var foo = function () {}
複製程式碼
ArrowFunctionExpression
箭頭函式表示式
interface ArrowFunctionExpression {
type: 'ArrowFunctionExpression';
id: Identifier | null;
params: FunctionParameter[];
body: BlockStatement | Expression;
generator: boolean;
async: boolean;
expression: false;
}
複製程式碼
generator
表示是否為generator函式,async
表示是否為async/await函式,params
為引數定義。
FunctionParameter
型別如下
type FunctionParameter = AssignmentPattern | Identifier | BindingPattern;
複製程式碼
例:
var foo = () => {};
複製程式碼
ClassExpression
類表示式
interface ClassExpression {
type: 'ClassExpression';
id: Identifier | null;
superClass: Identifier | null;
body: ClassBody;
}
複製程式碼
例如:
var foo = class {
constructor() {}
method() {}
};
複製程式碼
TaggedTemplateExpression
標記模板文字函式
interface TaggedTemplateExpression {
type: 'TaggedTemplateExpression';
readonly tag: Expression;
readonly quasi: TemplateLiteral;
}
複製程式碼
TemplateLiteral
型別如下
interface TemplateLiteral {
type: 'TemplateLiteral';
quasis: TemplateElement[];
expressions: Expression[];
}
複製程式碼
TemplateElement
型別如下
interface TemplateElement {
type: 'TemplateElement';
value: { cooked: string; raw: string };
tail: boolean;
}
複製程式碼
例如
var foo = function(a){ console.log(a); }
foo`test`;
複製程式碼
MemberExpression
屬性成員表示式
interface MemberExpression {
type: 'MemberExpression';
computed: boolean;
object: Expression;
property: Expression;
}
複製程式碼
例如:
const foo = {bar: 'bar'};
foo.bar;
foo['bar']; // computed 為 true
複製程式碼
Super
父類關鍵字
interface Super {
type: 'Super';
}
複製程式碼
例如:
class foo {};
class bar extends foo {
constructor() {
super();
}
}
複製程式碼
MetaProperty
(這個不知道幹嘛用的)
interface MetaProperty {
type: 'MetaProperty';
meta: Identifier;
property: Identifier;
}
複製程式碼
例如:
new.target // 通過new 宣告的物件,new.target會存在
import.meta
複製程式碼
CallExpression
函式執行表示式
interface CallExpression {
type: 'CallExpression';
callee: Expression | Import;
arguments: ArgumentListElement[];
}
複製程式碼
Import型別,沒搞懂。
interface Import {
type: 'Import'
}
複製程式碼
ArgumentListElement
型別
type ArgumentListElement = Expression | SpreadElement;
複製程式碼
如:
var foo = function (){};
foo();
複製程式碼
NewExpression
new 表示式
interface NewExpression {
type: 'NewExpression';
callee: Expression;
arguments: ArgumentListElement[];
}
複製程式碼
UpdateExpression
更新操作符表示式,如++
、--
;
interface UpdateExpression {
type: "UpdateExpression";
operator: '++' | '--';
argument: Expression;
prefix: boolean;
}
複製程式碼
如:
var i = 0;
i++;
++i; // prefix為true
複製程式碼
AwaitExpression
await表示式,會與async連用。
interface AwaitExpression {
type: 'AwaitExpression';
argument: Expression;
}
複製程式碼
如
async function foo() {
var bar = function() {
new Primise(function(resolve, reject) {
setTimeout(function() {
resove('foo')
}, 1000);
});
}
return await bar();
}
foo() // foo
複製程式碼
UnaryExpression
一元操作符表示式
interface UnaryExpression {
type: "UnaryExpression";
operator: UnaryOperator;
prefix: boolean;
argument: Expression;
}
複製程式碼
列舉UnaryOperator
enum UnaryOperator {
"-" | "+" | "!" | "~" | "typeof" | "void" | "delete" | "throw"
}
複製程式碼
BinaryExpression
二元操作符表示式
interface BinaryExpression {
type: 'BinaryExpression';
operator: BinaryOperator;
left: Expression;
right: Expression;
}
複製程式碼
列舉BinaryOperator
enum BinaryOperator {
"==" | "!=" | "===" | "!=="
| "<" | "<=" | ">" | ">="
| "<<" | ">>" | ">>>"
| "+" | "-" | "*" | "/" | "%"
| "**" | "|" | "^" | "&" | "in"
| "instanceof"
| "|>"
}
複製程式碼
LogicalExpression
邏輯運算子表示式
interface LogicalExpression {
type: 'LogicalExpression';
operator: '||' | '&&';
left: Expression;
right: Expression;
}
複製程式碼
如:
var a = '-';
var b = a || '-';
if (a && b) {}
複製程式碼
ConditionalExpression
條件運算子
interface ConditionalExpression {
type: 'ConditionalExpression';
test: Expression;
consequent: Expression;
alternate: Expression;
}
複製程式碼
例如:
var a = true;
var b = a ? 'consequent' : 'alternate';
複製程式碼
YieldExpression
yield表示式
interface YieldExpression {
type: 'YieldExpression';
argument: Expression | null;
delegate: boolean;
}
複製程式碼
例如:
function* gen(x) {
var y = yield x + 2;
return y;
}
複製程式碼
AssignmentExpression
賦值表示式。
interface AssignmentExpression {
type: 'AssignmentExpression';
operator: '=' | '*=' | '**=' | '/=' | '%=' | '+=' | '-=' |
'<<=' | '>>=' | '>>>=' | '&=' | '^=' | '|=';
left: Expression;
right: Expression;
}
複製程式碼
operator
屬性表示一個賦值運算子,left
和right
是賦值運算子左右的表示式。
SequenceExpression
序列表示式(使用逗號)。
interface SequenceExpression {
type: 'SequenceExpression';
expressions: Expression[];
}
複製程式碼
var a, b;
a = 1, b = 2
複製程式碼
ArrayPattern
陣列解析模式
interface ArrayPattern {
type: 'ArrayPattern';
elements: ArrayPatternElement[];
}
複製程式碼
例:
const [a, b] = [1,3];
複製程式碼
elements代表陣列節點
ArrayPatternElement如下
type ArrayPatternElement = AssignmentPattern | Identifier | BindingPattern | RestElement | null;
複製程式碼
AssignmentPattern
預設賦值模式,陣列解析、物件解析、函式引數預設值使用。
interface AssignmentPattern {
type: 'AssignmentPattern';
left: Identifier | BindingPattern;
right: Expression;
}
複製程式碼
例:
const [a, b = 4] = [1,3];
複製程式碼
RestElement
剩餘引數模式,語法與擴充套件運算子相近。
interface RestElement {
type: 'RestElement';
argument: Identifier | BindingPattern;
}
複製程式碼
例:
const [a, b, ...c] = [1, 2, 3, 4];
複製程式碼
ObjectPatterns
物件解析模式
interface ObjectPattern {
type: 'ObjectPattern';
properties: Property[];
}
複製程式碼
例:
const object = {a: 1, b: 2};
const { a, b } = object;
複製程式碼
結束
AST的作用大致分為幾類
-
IDE使用,如程式碼風格檢測(eslint等)、程式碼的格式化,程式碼高亮,程式碼錯誤等等
-
程式碼的混淆壓縮
-
轉換程式碼的工具。如webpack,rollup,各種程式碼規範之間的轉換,ts,jsx等轉換為原生js
瞭解AST,最終還是為了讓我們瞭解我們使用的工具,當然也讓我們更瞭解JavaScript,更靠近JavaScript。