js抽象语法树

function add(a, b) {
    return a + b
}

这个语法块,是一个FunctionDeclaration(函数定义)对象。拆解之后包括

  • 一个id,就是它的名字,即add
  • 两个params,就是它的参数,即[a, b]
  • 一个body,也就是大括号内的一堆东西

add没办法继续拆下去了,它是一个最基础Identifier(标志)对象,用来作为函数的唯一标志

{
    name: 'add'
    type: 'identifier'
    ...
}

params继续拆下去,其实是两个Identifier组成的数组。

[
    {
        name: 'a'
        type: 'identifier'
        ...
    },
    {
        name: 'b'
        type: 'identifier'
        ...
    }
]

我们发现,body其实是一个BlockStatement(块状域)对象,用来表示是{return a + b}
打开Blockstatement,里面藏着一个ReturnStatement(Return域)对象,用来表示return a + b
继续打开ReturnStatement,里面是一个BinaryExpression(二项式) 对象,用来表示a + b
继续打开BinaryExpression,表达式成了三部分,left,operator,right

  • operator 即+
  • left 里面装的,是Identifier对象 a
  • right 里面装的,是Identifer对象 b

抽象语法树(Abstract Syntax Tree)就是这样一步步构建出来的。AST对象文档(https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Parser_API#Node_objects)

解析库recast

使用npm安装解析库recast

// recast

const recast = require("recast");

// 你的"机器"——一段代码
const code = `function add(a, b) {return a +
      b;
  }
`;

// 用螺丝刀解析机器

const ast = recast.parse(code);

// ast可以处理很巨大的代码文件

// 但我们现在只需要代码块的第一个body,即add函数

const add = ast.program.body[0];

console.log(add);

会得到这样的结果

FunctionDeclaration {
  type: 'FunctionDeclaration',
  id: 
   Identifier {
     type: 'Identifier',
     name: 'add',
     loc: { start: [Object], end: [Object], lines: [Lines], indent: 0 } },
  params: 
   [ Identifier { type: 'Identifier', name: 'a', loc: [Object] },
     Identifier { type: 'Identifier', name: 'b', loc: [Object] } ],
  body: 
   BlockStatement {
     type: 'BlockStatement',
     body: [ [ReturnStatement] ],
     loc: { start: [Object], end: [Object], lines: [Lines], indent: 0 } },
  generator: false,
  expression: false,
  async: false,
  loc: 
   { start: { line: 1, column: 0 },
     end: { line: 3, column: 3 },
     lines: Lines { length: 4, name: null, [Symbol(recastLinesSecret)]: [Object] },
     indent: 0 } }

recast.types.builders 制作模具

最简单的例子,我们想把之前的function add(a, b){...}声明,改成匿名函数式声明const add = function(a ,b){...}
如何改装?
第一步,我们创建一个VariableDeclaration变量声明对象,声明头为const, 内容为一个即将创建的VariableDeclarator对象。

第二步,创建一个VariableDeclarator,放置add.id在左边, 右边是将创建的FunctionDeclaration对象

第三步,我们创建一个FunctionDeclaration,如前所述的三个组件,id params body中,因为是匿名函数id设为空,params使用add.params,body使用add.body。(通过id可以唯一标识一个函数)

这样,就创建好了const add = function(){}的AST对象。

// recast

const recast = require("recast");
// 引入变量声明,变量符号,函数声明三种“模具”
const {variableDeclaration, variableDeclarator, functionExpression} = recast.types.builders

// 你的"机器"——一段代码
const code = `function add(a, b) {return a +
      b;
  }
`;

// 用螺丝刀解析机器

const ast = recast.parse(code);

const add = ast.program.body[0];

// 将准备好的组件置入模具,并组装回原来的ast对象。

ast.program.body[0] = variableDeclaration("const", [

  variableDeclarator(add.id, functionExpression(

    null, // Anonymize the function expression.

    add.params,

    add.body

  ))

]);

//将AST对象重新转回可以阅读的代码

const output = recast.print(ast).code;



console.log(output)

最后得到

const add = function(a, b) {return a +
    b;
};

命令行修改js文件

除了上面演示的parse/print/builder以外,Recast还有三项主要功能

  • run: 通过命令行读取js文件,并转化成ast以供处理。
  • tnt: 通过assert()和check(),可以验证ast对象的类型。
  • visit: 遍历ast树,获取有效的AST对象并进行更改

猜你喜欢

转载自blog.csdn.net/sysuzhyupeng/article/details/82529479
今日推荐