9

Abstract Syntax Tree 抽象语法树简介

 3 years ago
source link: https://zhuanlan.zhihu.com/p/26988179
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Abstract Syntax Tree 抽象语法树简介

在使用前端许多工具插件的时候,我们大多知道每个工具库、每个插件能做什么,不过很多同学其实并不清楚背后用到的技术,如webpack、rollup、UglifyJS、Lint等很多的工具和库的核心都是通过Abstract Syntax Tree 抽象语法树这个概念来实现对代码的检查、分析等操作的。通过了解抽象语法树这个概念,你也可以随手编写类似的工具,发现一个新的世界。

Abstract Syntax Tree 抽象语法树定义

理论的知识总是有些枯燥乏味,不过客官别急,一步一步来。

其实这些工具的原理都是通过JavaScript Parser把代码转化为一颗抽象语法树(AST),这颗树定义了代码的结构,通过操纵这颗树,我们可以精准的定位到声明语句、赋值语句、运算语句等等,实现对代码的分析、优化、变更等操作。

wikipedia定义:

In computer science, an abstract syntax tree (AST), or just syntax tree, is a tree representation of the abstract syntactic structure of source code written in a programming language.

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

Javascript的语法是为了给开发者更好的编程而设计的,但是不适合程序的理解。所以需要转化为AST来更适合程序分析,浏览器编译器一般会把源码转化为AST来进行进一步的分析等其他操作。

以下只介绍Javascript相关的抽象语法树

比如说有一段代码:

var a = 3;
a + 5;

那么它的抽象语法树就类似:

JavaScript Parser

JavaScript Parser,把js源码转化为抽象语法树的解析器。

浏览器会把js源码通过解析器转为抽象语法树,再进一步转化为字节码或直接生成机器码。

一般来说每个js引擎都会有自己的抽象语法树格式,Chrome的v8引擎,firefox的SpiderMonkey引擎等等,MDN提供了详细SpiderMonkey AST format的详细说明,算是业界的标准。

发展到现在可能不同的JavaScript Parser的AST格式会不同,或基于SpiderMonkey AST format,或重新设计自己的AST format,或基于SpiderMonkey AST format优化改进。通过优化抽象语法树,来使程序运行的更快,也是一种提高效率的方法。

常用的JavaScript Parser有:

  • Esprima
  • UglifyJS2
  • Traceur
  • Acorn
  • Shift

在Esprima的官网有一个比较各个Parser解析速度的列表Speed Comparison。 看下来Acorn是公认的最快的。

UglifyJS2的作者自己实现了一套js的抽象语法树,用到了继承,和现有的扁平的抽象语法树都有所不同,但作者也提供使用不同的抽象语法树来解析代码。

AST explorer可以在线看到不同的parser解析js代码后得到的AST。

JavaScript AST visualizer 可以在线可视化的看到AST。

生成并使用抽象语法树

通过 esprima , 把一个名字为ast的空函数的源码生成一颗AST树:

    var esprima = require('esprima');
    var code = 'function ast(){}';
    var ast = esprima.parse(code);

生成的抽象语法树长这样:

{
  "type": "Program",
  "body": [
    {
      "type": "FunctionDeclaration",
      "id": {
        "type": "Identifier",
        "name": "ast",
        "range": [
          9,
          12
        ]
      },
      "params": [],
      "body": {
        "type": "BlockStatement",
        "body": [],
        "range": [
          14,
          16
        ]
      },
      "generator": false,
      "expression": false,
      "range": [
        0,
        16
      ]
    }
  ],
  "sourceType": "module",
  "range": [
    0,
    16
  ]
}

通过 estraverse 遍历并且更新抽象语法树,把函数名称改为ast_awsome:

    ...
    var estraverse = require('estraverse');
    estraverse.traverse(ast, {
        enter: function (node) {
            node.name += "_awsome";
        }
    });

通过 escodegen 将AST重新生成为源码:

    ...
    var escodegen = require("escodegen");
    var regenerated_code = escodegen.parse(ast)

AST三板斧:

  1. 通过 esprima 把源码转化为AST
  2. 通过 estraverse 遍历并更新AST
  3. 通过 escodegen 将AST重新生成源码

抽象语法树的用途

浏览器最先就会把源码解析为抽象语法树,对浏览器而言AST的作用非常重要。

对开发者而言,AST的作用就是可以精准的定位到代码的任何地方,它就像是是你的手术刀,对代码进行一系列的操作。

常见的几种用途:

  • 代码语法的检查、代码风格的检查、代码的格式化、代码的高亮、代码错误提示、代码自动补全等等
    • 如JSLint、JSHint对代码错误或风格的检查,发现一些潜在的错误
    • IDE的错误提示、格式化、高亮、自动补全等等
  • 代码混淆压缩
    • UglifyJS2等
  • 优化变更代码,改变代码结构使达到想要的结构
    • 代码打包工具webpack、rollup等等
    • CommonJS、AMD、CMD、UMD等代码规范之间的转化
    • CoffeeScript、TypeScript、JSX等转化为原生Javascript

抽象语法树在前端领域中的应用广泛,通过抽象语法树大家可以实现很多功能,发现编写工具提高效率带来的乐趣。

Understanding ASTs by Building Your Own Babel Plugin
UglifyJS — why not switching to SpiderMonkey AST
A Technical Comparison of the Shift and SpiderMonkey AST Formats
SpiderMonkey Parser API

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK