JavaScript抽象语法树及其应用

抽象语法树

又叫AST或者语法树,是源代码抽象语法结构的树形式。AST的生成规则主要是通过分析代码中的关键字、操作符、变量等类型以及代码的结构,生成出来,当然会忽视一些括号、分号等内容,这些东西在AST上是找不到的。通过操作AST,可以获取到节点,精准的定位到声明语句、赋值语句、运算语句等等,实现对代码的分析、优化、变更等操作。

抽象语法树作为语言底层原理,与编译器、代码优化、代码转换等有重要关系。

JavaScript抽象语法树

JavaScript的语法树生成可以通过这个网站的工具 http://esprima.org/ ,生成一个json文件进行描述。
一些常见的赋值语句与判断语句的AST是下面这样:

foo = 'hello world';
/*
+-------------+
|  assign(=)  |
+-------------+
X        X
X          X
+-------+    +-----------------+
|  foo  |    |  'hello world'  |
+-------+    +-----------------+
*/
if (foo === true) {
  bar = 'hello world';
  alert(bar);
}
/*
+------+
|  if  |
+------+
X    X
X        X
+--------------+    +-------------+
|  equal(===)  |    |  if_body    |
+--------------+    +-------------+
X        X              X         X
X         X                X          X
+-------+   +--------+    +-------------+   +------------+
|  foo  |   |  true  |    |  assign(=)  |   |  alert()   |
+-------+   +--------+    +-------------+   +------------+
X        X                  X
X            X                  X
                       +-------+   +-----------------+    +-------+
                       |  bar  |   |  'hello world'  |    |  bar  |
                       +-------+   +-----------------+    +-------+
*/

很多现代前端的工具,其底层原理是JavaScript的AST,比如Webpack、UglifyJS、Lint、Babel、TS、JSX等等都有应用。<br>
ps:最早我以为这类工具是通过正则做到的,原理是正则匹配到相关代码,然后将其转换,后来锋哥聊他用QT写的一个编辑器(https://github.com/rhythm1995/Nider )的时候,他说道代码高亮最早是用监听空格然后关键字判断这种爆搜的方式做的,性能低下,后来写过类似工具的天哥说道AST,然后最早了解。

UglifyJS

UglifyJS是主要的JavaScript压缩与代码混淆工具,其本身就是一个AST分析工具!通过UglifyJS对JavaScript代码的压缩,可以极大的降低文件大小从而提升前端性能。
可以干的事情有:

  • 字符串解析成AST
  • 解析AST,计算出作用域、变量名等
  • 提供遍历AST的方法,找到你想要修改的节点
  • 提供把AST打印成源代码的功能
  • 提供美化代码的功能
  • 提供定位某个token位置的功能

压缩原理大概是,生成语法树后把不重要的都删掉。
混淆原理稍微复杂,用下边代码解释。

1、约定规则,符串怎么混?变量名怎么混?函数名怎么混?逻辑操作符变函数?这里约定了规则,变量名a,b,c依次往后,估计就没人读得懂了。如:
混淆前:
var Global = "我是一个全局变量";
function test() { //该方法中没有用过Global
var left = "我是一个局部变量";
var right = "我是一个局部变量";
var temp = "我是一个局部变量";
}
混淆后:
var a = "我是一个全局变量";
function test() { //该方法中没有用过Global
var a = "我是一个局部变量";
var b = "我是一个局部变量";
var b = "我是一个局部变量";
}
2、生成AST

我用第一段提到的工具生成json文件描述,如下:

{
    "type": "Program",
    "body": [
        {
            "type": "VariableDeclaration",
            "declarations": [
                {
                    "type": "VariableDeclarator",
                    "id": {
                        "type": "Identifier",
                        "name": "Global"
                    },
                    "init": {
                        "type": "Literal",
                        "value": "我是一个全局变量",
                        "raw": "\"我是一个全局变量\""
                    }
                }
            ],
            "kind": "var"
        },
        {
            "type": "FunctionDeclaration",
            "id": {
                "type": "Identifier",
                "name": "test"
            },
            "params": [],
            "body": {
                "type": "BlockStatement",
                "body": [
                    {
                        "type": "VariableDeclaration",
                        "declarations": [
                            {
                                "type": "VariableDeclarator",
                                "id": {
                                    "type": "Identifier",
                                    "name": "left"
                                },
                                "init": {
                                    "type": "Literal",
                                    "value": "我是一个局部变量",
                                    "raw": "\"我是一个局部变量\""
                                }
                            }
                        ],
                        "kind": "var"
                    },
                    {
                        "type": "VariableDeclaration",
                        "declarations": [
                            {
                                "type": "VariableDeclarator",
                                "id": {
                                    "type": "Identifier",
                                    "name": "right"
                                },
                                "init": {
                                    "type": "Literal",
                                    "value": "我是一个局部变量",
                                    "raw": "\"我是一个局部变量\""
                                }
                            }
                        ],
                        "kind": "var"
                    },
                    {
                        "type": "VariableDeclaration",
                        "declarations": [
                            {
                                "type": "VariableDeclarator",
                                "id": {
                                    "type": "Identifier",
                                    "name": "temp"
                                },
                                "init": {
                                    "type": "Literal",
                                    "value": "我是一个局部变量",
                                    "raw": "\"我是一个局部变量\""
                                }
                            }
                        ],
                        "kind": "var"
                    }
                ]
            },
            "generator": false,
            "expression": false,
            "async": false
        }
    ],
    "sourceType": "script"
}
3、对AST操作

生成AST后,树种节点与分支就确定了变量、操作符与作用域。这里把同一作用域的name替换即可完成这条规则。

Babel

Babel是把ES6转成ES5的工具,蠢萌的我曾经看过babel转换后的代码,当时的理解就是let,const转var;=>转;模板字符串转字符串拼接。。。后来提升了姿势水平,知道了AST这个高大上的东西,才明白babel原理:

  • 1.babylon进行解析,得到源代码AST
  • 2.plugin用babel-traverse对AST树进行遍历转译,得到新的AST
  • 3.用babel-generator通过AST树生成ES5代码

简直神奇!
ts过程大概和babel类似,不在总数,而webpack因为本身太复杂,之后再学习的时候再总结。
参考文章:http://www.iteye.com/news/30731,http://blog.csdn.net/dear_mr/article/details/72587908

发表评论

电子邮件地址不会被公开。 必填项已用*标注

😉😐😡😈🙂😯🙁🙄😛😳😮:mrgreen:😆💡😀👿😥😎😕