高階前端基礎-JavaScript抽象語法樹AST

麥樂丶發表於2019-03-16

前言

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;
複製程式碼

第一步詞法分析之後長成如下圖所示:

高階前端基礎-JavaScript抽象語法樹AST

語法分析,生產抽象語法樹,生成的抽象語法樹如下圖所示

高階前端基礎-JavaScript抽象語法樹AST

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

型別StatementListItemModuleItem型別如下。

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屬性表示一個賦值運算子,leftright是賦值運算子左右的表示式。

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的作用大致分為幾類

  1. IDE使用,如程式碼風格檢測(eslint等)、程式碼的格式化,程式碼高亮,程式碼錯誤等等

  2. 程式碼的混淆壓縮

  3. 轉換程式碼的工具。如webpack,rollup,各種程式碼規範之間的轉換,ts,jsx等轉換為原生js

瞭解AST,最終還是為了讓我們瞭解我們使用的工具,當然也讓我們更瞭解JavaScript,更靠近JavaScript。

參考文獻

相關文章