从如今起-完全学会 js ast

0afc5d1784e3414ad8aa93c6b74929bf.png
这是一棵树嘛javascript

直奔主题

抽象语法树是js代码另外一种结构映射,能够将js拆解成AST,也能够把AST转成源代码。这中间的过程就是咱们的用武之地。 利用 抽象语法树(AST) 能够对你的源代码进行修改、优化,甚至能够打造本身的编译工具。其实有点相似babel的功能。html

AST高深的狠吓人?

AST很简单,并无你想象的那样高深。不少地方都把这个技术给夸大了,什么编译原理,抽象语法树 光看这名字就以为吓人。固然一项技术总归要起个名字,就像给本身的孩子取名字,确定要起一个高大上,深有寓意的名字。因此,名字只是一个代号。从名字来看就会让不少人望而却步。可是ast超级简单,可是功能超级强大。java

咱们能用这个技术作不少有意思的东西,只要你能想到的。node

本文术道结合,让你感觉到ast的有趣和简单,今后爱上ast,还能根据本身的须要打造本身的编译器。webpack

什么是AST?

ast全称是abstract syntax tree,翻译过来叫-抽象语法树。其实这含两个意思,一个是“抽象”,一个是“树”。抽象表示把js代码进行告终构化的转化,转化为一种数据结构。这种数据结构其实就是一个大的json对象,json咱们都熟悉,他就像一颗枝繁叶茂的树。git

有树根,有树干,有树枝,有树叶.不管多小多大,都是一棵完整的树。github

如何生成AST?

你能够大体的想一下若是亲自实现把js代码转换成结构化的数据咱们应该怎么作?web

有点像小时候拆解本身的玩具,每一个零件之间都有着从属关系。express

对于如何生成ast,咱们可能会想到分析js代码的规则使用字符串处理、正则匹配等方法,若是对简单的代码处理咱们是能够实现的。可是若是可以对随意的一段代码进行处理那就须要考虑很是多的状况。具体如何实现我们没必要过于纠结,这也不是重点。json

但最终的实现里咱们能想到方法基本都会被用到。咱们能够简化理解,也就是对js代码通过了一系列的加工处理,变成了一堆零件或者食材(像老妈给咱们作的香喷喷的饭菜,但前提是先准备好菜)。

这个拆解的过程可能较为复杂,因此咱们须要用现成方法,直接拿过来用就能够了。

因此咱们须要用到esprima、UglifyJS等库,作菜的食材有不少种,因此会存在不少这样的三方库,而咱们会使用其中一种就能够了。

先使用esprima 种菜,体会一下

种子:

//源代码
function fun(a,b){
  
}

成熟:

{
            "type": "FunctionDeclaration",//函数声明
            "id": {
                "type": "Identifier",//标识符
                "name": "fun" //函数名称
            },
            "params": [//函数参数
                {
                    "type": "Identifier",//参数标识符
                    "name": "a"//参数名称
                },
                {
                    "type": "Identifier",
                    "name": "b"
                }
            ],
            "body": {//函数体
                "type": "BlockStatement",//语句块儿
                "body": []//具体内容为空,由于是空方法
            }
      }

有了AST能作什么?

到这一步你已经能够把js代码转换成一棵结构化的树了,那下一步要作什么呢? 好比在没有树的状况下,你要对代码里的某个代码进行替换。要把全部 console.log给注释掉或者删除,你可能会使用IDE的查找替换或者用node写一个方法,读取文件而后查找替换。

这种方式不够安全也不够科学,稍有不慎就会把代码给搞坏了。

但这个时候你有告终构化代码树,是否是只要对这棵树进行修修剪剪而后把这棵树转换成为js代码就能够了呢?

答案:确定是能够的。由于树已经发生了变化,修改了树就至关于修改了源码。

怎样操做这棵树呢?我想你应该已经知道了,就是对这json对象进行操做,方法就多了去了,前提是你得有一点点js基础。

又一个问题,怎样把树再转成代码?

脑洞打开,用递归加字符串拼接,这个方法应该是能够的。

可是这棵树不是你生成的,结构特色你并不清楚,成千上万个节点呢?怎么拼接?真要干,那可能得搞得流鼻血。

这就像是食材准备好了,转换成源码的过程就是炒菜的过程。具体的转源码的原理很少说,也没必要纠结。使用现成的方法就能够,因此要用到estraverse,escodegen这两个库。

estraverse 能够遍历树的全部节点,省去你对树的递归遍历

escodegen 能够把树再加工转成源代码

过程总结

到这里始终都没有提到任何代码,只是理论了一番,可是相信你已经理解了ast以及ast的做用。而后在述说过程当中引出了3个库,有了这三个库就能够对你的js代码进行多样化处理,只要你能想到的。

看图理解整个处理过程:

4226719341e4d0193ae3882e3dfc30e6.png@wm_2,t_55m+5a625Y+3L+WJjeerr+efpemBkw==,fc_ffffff,ff_U2ltSGVp,sz_20,x_13,y_13

这个过程简单,清晰,因此说ast简单、有趣、好玩。由于此刻代码能够被你任意的蹂躏了。

实例应用

说的再清楚都不够直观,毕竟都是脑补,不如看代码来的爽快。

这里就拿平常编码中的一些小问题举例,来演示一下AST的使用。

  1. 把 == 改成全等 ===
  2. 把parsetInt不标准的调用改成标准用法 parseInt(a)-> parseInt(a,10)

这里我使用esprima的官方工具生成了ast,工具地址http://esprima.org/demo/parse...

看下要处理的源码:

//源码
function fun1() {
    console.log('fun1');
}
function fun2(opt) {
    if (opt.status == 1) {
        console.log('1');
    }
    if (opt.status == 2) {
        console.log('2');
    }
}
function fun3(age) {
    if (parseInt(age) >= 18) {
        console.log('ok 你已经成年');
    }
}

转成ast,因为转成树后结构很是大,因此这里我只贴了一部分,你也能够到工具页面本身生成下。

{
    "type": "Program",
    "body": [
        {
            "type": "FunctionDeclaration",
            "id": {
                "type": "Identifier",
                "name": "fun1"
            },
            "params": [],
            "body": {
                "type": "BlockStatement",
                "body": [
                    {
                        "type": "ExpressionStatement",
                        "expression": {
                            "type": "CallExpression",
                            "callee": {
                                "type": "MemberExpression",
                                "computed": false,
                                "object": {
                                    "type": "Identifier",
                                    "name": "console"
                                },
                                "property": {
                                    "type": "Identifier",
                                    "name": "log"
                                }
                            },
                            "arguments": [
                                {
                                    "type": "Literal",
                                    "value": "fun1",
                                    "raw": "'fun1'"
                                }
                            ]
                        }
                    }
                ]
            },
            "generator": false,
            "expression": false,
            "async": false
        }
    ]
}

ast看上去结构复杂,盯着仔细看后基本都能看懂。全部的代码都在特定的节点里面。具体的这里就不介绍了,能够到上面的工具地址去观察不一样的ast结构。总之这就是一个对象,只要你能对这个对象进行修改、添加、删除便可。

开始实现以上功能
init

//引入工具包
const esprima = require('esprima');//JS语法树模块
const estraverse = require('estraverse');//JS语法树遍历各节点
const escodegen = require('escodegen');//JS语法树反编译模块
//获�取代码ast
const AST = esprima.parseScript(jsCode);

/**
 * 
 * @param {遍历语法树} ast 
 */
function walkIn(ast){
    estraverse.traverse(ast, {
        enter: (node) => {
            toEqual(node);//把 == 改成全等 ===
            setParseint(node); //parseInt(a)-> parseInt(a,10)
        }
    });
}

2.把 == 改成全等 ===

/**
 * 设置全等
 */
function toEqual(node) {
    if (node.operator === '==') {
        node.operator = '===';
    }
}
  1. 把parseInt改为标准调用
/**
 * 把parseint改成标准方法
 * @param {节点} node 
 */
function setParseint(node) {
    //判断节点类型 方法名称,方法的参数的数量,数量为1就增长第二个参数
    if (node.type === 'CallExpression' && node.callee.name === 'parseInt' && node.arguments.length===1){

        node.arguments.push({//增长参数,其实就是数组操做
            "type": "Literal",
            "value": 10,
            "raw": "10"
        });
    }
}

//生成目标代码
const code = escodegen.generate(ast);
//写入文件.....
//....你懂的

代码很少,需求简单,但已足够能说明整个处理过程以及ast的强大。 ast的节点不少,有些凌乱,送你一首歌【汪峰的无所谓】,操做的时候只要关心你本身的需求就能够,不须要对全部的节点都搞明白。按需处理就能够。

AST技术的应用

虽然平时用不到ast,但又时刻都在使用ast技术。家喻户晓、无人不知的babel,webpack,还有jd taro等都把ast用的淋漓尽致,脱离了ast他们就跪了。

AST这么简单,好没技术含量
AST没有技术含量吗?怎么可能呢,若是真这么认为怕是会被笑掉大牙的。若是仅仅停留在使用层面的话,理解到这步已经基本能够了,只要是你能对这棵树作修剪就能够对源代码作手脚。

另外ast怎样生成的?怎样把ast转换成源码的?这就有点高深了。会使用就像是在山脚下能看到的风景有限,理解了背后原理机制就像是爬上了山顶,别样的风景一览无余。不过上不上山看我的兴趣,有兴趣的同窗能够去看源码、作研究,这里就再也不多说,由于我也不知道。哈哈哈

总结

本文主要介绍了

什么是ast:

ast其实就把js代码进行抽象为一种json结构;

ast的用途:

利用ast能够方便的优化和修改代码,还能打造本身的编译器;

而后经过具体的示例演示了怎样操做ast,最终是但愿你能对ast有一个系统全局的认识和理解并可以利用ast打造本身的编译工具。

演示代码下载,欢迎star

https://github.com/bigerfe/fo...

自家观点,欢迎打脸

原创不易,请多鼓励

相关文章
相关标签/搜索