张大发一来上班,领导就给他丢了一个任务,项目的JavaScript代码做了一些“小”改动:1.把==改成全等===2.把非标准改成调用parseInt的标准用法parseInt(xxx)->parseInt(xxx,10)不熟悉JS的同学稍微解释一下:JS比较两个变量时,双等号会进行类型转换;三重等号将进行相同的比较,而不进行类型转换(如果类型不同,则始终返回false);parseInt(a,10)表示以十进制解析。对于这些任务,张大发的脑海里立刻闪过一个解决方案:字符串替换。对于第一个任务:找到“==”并将其替换为“===”。对于第二个任务:把parseInt(xxx)改成parseInt(xxx,10),没办法直接替换,只好写一个正则表达式,找出那些只有一个参数的parseInt字符串,添加一个新的参数:10。张大胖对自己的正则表达式能力不是很自信。如果他不仔细考虑,代码可能会被更改。还有别的办法吗?01抽象语法树使用正则表达式,只能把JavaScript源代码当作文本。能力很弱,摸不到JavaScript的语法层面。正则表达式无法知道这个地方是变量,那个地方是函数名……如果能把JavaScript源代码转换成结构化对象,就可以准确知道一个变量名、函数名、参数在一个一段代码……这样,你就可以写出准备处理的程序了。张胖子想起自己没考上《编译原理》。里面提到抽象语法树(AST)不是所谓的结构化的东西吗?比如表达式result=6+7*3,用抽象语法树表示就是:如果把所有的JavaScript代码都转换成这样一棵AST树,那么代码中的一切都在掌控之中,可以任意修改。但是存在三个问题:1.如何从文本形式的源代码中形成这样的AST?自己写程序太难了,还得做词法分析,语法分析等。2、如何遍历AST修改树的枝叶?比如我想给AST树增加一个新的节点,应该怎么做呢?3、修改完成后,如何将AST再次转为文本源代码?张胖子赶紧打开谷歌搜索,很快找到了三个开源工具,刚好完成了对应的三个功能:esprima:FormASTestraversefromJavaScriptsourcecode:遍历树的节点并修改escodegen:convert修改后的AST转换为source再次编码。02一说就创建AST,张大胖准备了一段代码来做实验://源代码functionfun1(opt){if(opt.status==1){console.log('1');}if(opt.status==2){console.log('2');}}functionfun2(age){if(parseInt(age)>=18){console.log('好的你是成年人');}}使用esprima,很容易转换成抽象语法树。//JS语法树模块constesprima=require('esprima');//创建ASTconstAST=esprima.parseScript(jsCode);(因为转换成树后结构很大,这里就不展示了,有兴趣的同学可以去http://esprima.org/demo/parse.html玩玩很好玩。)例如:if(parseInt(age)>=18)这句话转化为:03遍历和修改ASTAST,可以简单的遍历和修改,或者使用开源工具。//JS语法树遍历每个节点contestraverse=require('estraverse');//从JS语法树生成源码constescodegen=require('escodegen');functionwalkIn(ast){estraverse.traverse(ast,{enter:(node)=>{toEqual(node);//把==改成equal===setParseInt(node);//parseInt(a)->parseInt(a,10)}});}这个函数负责Change'=='to'==='functiontoEqual(node){if(node.operator==='=='){node.operator='===';}}这个函数负责把parseInt改成标准的调用:functionsetParseInt(node){//判断节点类型方法名,方法参数个数,如果个数为1则添加第二个参数。if(node.type==='CallExpression'&&node.callee.name==='parseInt'&&node.arguments.length===1){node.arguments.push({//增加参数其实是一个数组操作》type":"Literal","value":10,"raw":"10"});}}这个函数之后,原来的if(parseInt(age)>=18)就变成了下图这样,相当Sincea添加节点,对应代码为:if(parseInt(age,10)>=18)***使用escodegen再次将修改后的AST转为源码,大功告成://Generateobjectcodeconstcode=escodegen.generate(ast);//写入文件.....//...你知道的。通过这次实验,张大发基本了解了AST的原理和用法,然后就可以开始正式的编程了。04小结本文中的例子可能不是使用AST的最佳方案,主要是为了演示AST的处理技术,AST其实是源代码的结构化表示,使用它和相关工具可以很方便的对代码进行优化和修改,只要如果你能“修剪”这棵“AST树”,你就可以对源码做各种“手脚”:JavaScript代码语法、风格检查、IDE中的错误提示、代码的自动补全、压缩重构、混淆改造code...如此强大的功能,AST处理技术是很多知名工具的基础,比如babel、webpack、jdtaro等,都大量使用了AST。【本文为专栏作家“刘欣”原创稿件,转载请通过作者微信获取授权公众号编码】点此查看该作者更多好文
