总结不同的混淆,可以搭配使用,有增加代码量、使执行流程混乱、使眼睛看起来花…,但它们都是给逆向者增加分析难度完整代码:https://pan.bigdataboy.cn/s/G2Msx验证源代码未混淆Date.prototype.format=function(formatStr){varstr=formatStr;str=str.replace(/yyyy|YYYY/,this.getFullYear());str=str.replace(/MM/,this.getMonth()+1>9?(this.getMonth()+1).toString():'0'+(this.getMonth()+1));//encryptAsciistr=str.replace(/dd|DD/,this.getDate()>9?this.getDate().toString():'0'+this.getDate());//encryptreturnstr;};console.log(newDate().format('yyyy-MM-dd'));混淆后vararr=["cHJvdG90eXBl","Zm9ybWF0","cmVwbGFjZQ==","Z2V0RnVsbFllYXI=","Z2V0TW9udGg=","dG9TdHJpbmc=","MA==","Z2V0RGF0ZQ==","Y29uc29sZQ==","bG9n","eXl5eS1NTS1kZA==","RGF0ZQ=="];((arr,nums)=>{while(--nums){arr["\x70\x75\x73\x68"](arr["\x73\x68\x69\x66\x74"]());}})(arr,arr["\x6c\x65\x6e\x67\x74\x68"]);window[atob(arr[0])][atob(arr[1])][atob(arr[2])]=function(OOOOOO){var_array="6|14|4|13|15|5|8|0|12|1|16|7|10|3|2|9|11".split("|"),_index=0;while(!![]){switch(+_array[_index++]){case0:function_xxx5(a,b){returna+b;}continue;case1:function_xxx3(a,b){returna^b;}continue;case2:eval(String.fromCharCode(79,79,79,79,79,111,32,61,32,79,79,79,79,79,111,91,97,116,111,98,40,97,114,114,91,51,93,41,93,40,47,77,77,47,44,32,95,120,120,120,40,95,120,120,120,50,40,116,104,105,115,91,97,116,111,98,40,97,114,114,91,53,93,41,93,40,41,44,32,95,120,120,120,51,40,54,48,56,56,54,51,44,32,54,48,56,56,54,50,41,41,44,32,95,120,120,120,52,40,54,57,53,55,54,48,44,32,54,57,53,55,54,57,41,41,32,63,32,95,120,120,120,53,40,116,104,105,115,91,97,116,111,98,40,97,114,114,91,53,93,41,93,40,41,44,32,95,120,120,120,54,40,49,56,53,56,57,55,44,32,49,56,53,56,57,54,41,41,91,97,116,111,98,40,97,114,114,91,54,93,41,93,40,41,32,58,32,95,120,120,120,55,40,97,116,111,98,40,97,114,114,91,55,93,41,44,32,95,120,120,120,56,40,116,104,105,115,91,97,116,111,98,40,97,114,114,91,53,93,41,93,40,41,44,32,95,120,120,120,57,40,53,51,49,48,54,54,44,32,53,51,49,48,54,55,41,41,41,41,59));continue;case3:OOOOOo=OOOOOo[atob(arr[3])](/yyyy|YYYY/,this[atob(arr[4])]());continue;case4:function_xxx10(a,b){returna>b;}continue;case5:function_xxx7(a,b){returna+b;}continue;case6:function_xxx12(a,b){returna+b;}continue;case7:function_xxx(a,b){returna>b;}continue;case8:function_xxx6(a,b){returna^b;}continue;case9:eval(atob("T09PT09vID0gT09PT09vW2F0b2IoYXJyWzNdKV0oL2RkfERELywgX3h4eDEwKHRoaXNbYXRvYihhcnJbOF0pXSgpLCBfeHh4MTEoMjAxOTEyLCAyMDE5MDUpKSA/IHRoaXNbYXRvYihhcnJbOF0pXSgpW2F0b2IoYXJyWzZdKV0oKSA6IF94eHgxMihhdG9iKGFycls3XSksIHRoaXNbYXRvYihhcnJbOF0pXSgpKSk7"));continue;case10:varOOOOOo=OOOOOO;continue;case11://encryptreturnOOOOOo;continue;case12:function_xxx4(a,b){returna^b;}continue;case13:function_xxx9(a,b){returna^b;}continue;case14:function_xxx11(a,b){returna^b;}continue;case15:function_xxx8(a,b){returna+b;}continue;case16:function_xxx2(a,b){returna+b;}continue;}}};window[atob(arr[9])][atob(arr[10])](newwindow[atob(arr[0])]()[atob(arr[2])](atob(arr[11])));
特别说明:本文生成节点时,函数的使用对于Js来说是错误的(不能指定参数给值),这样写是方便看节点属性思路流程平坦化有好几种形式,结果都是打乱代码执行流程,原理都一样,while-switch、for-switch当然阿里极验的平坦流混淆,更复杂,这里只是最简单的,分发器还能看见while(!![]){//一个死循环switch(...){//每次循环改变switch的判断,而走不同的分支case"0":...case"1":...}}实现获取代码块,映射代码块的执行循序{index:i,value:代码块节点},然后打乱执行顺序,构造分发器,构建死循环,再构建switch分支,替换原来的代码块//流程平坦流混淆traverse(ast,{FunctionExpression(path){letblockStatement=path.node.body;//映射语句执行顺序letstatement=blockStatement.body.map((v,i)=>{return{index:i,value:v}})//流程打乱语句for(leti=1;i<statement.length;i++){constj=Math.floor(Math.random()*(i+1));[statement[i],statement[j]]=[statement[j],statement[i]];}//构建分发器,创建swichCase数组letdispenserArr=[];//流程分发数组letcases=[];//构建cases节点statement.map((v,i)=>{dispenserArr[v.index]=i;letswitchCase=type.switchCase(test=type.numericLiteral(value=i),consequent=[v.value,type.continueStatement()])cases.push(switchCase);})//生成_array和_index标识符,利用BableAPI保证不重名letarray=path.scope.generateUidIdentifier('array')letindex=path.scope.generateUidIdentifier('index')//生成var_array='0|2|2'.spiit('|'),index=0;节点letdispenserStr=dispenserArr.join('|');letdispenser=type.variableDeclaration(kind='var',declarations=[type.variableDeclarator(id=array,init=type.callExpression(callee=type.memberExpression(object=type.stringLiteral(value=dispenserStr),property=type.identifier(name='split')),_arguments=[type.stringLiteral(value='|')]),),type.variableDeclarator(id=index,init=type.numericLiteral(value=0))])//生成while-swiich节点letwhileSta=type.whileStatement(test=type.unaryExpression(//while循环条件节点operator='!',argument=type.unaryExpression(operator='!',argument=type.arrayExpression(elements=[]))),body=type.blockStatement(//swiich节点body=[type.switchStatement(discriminant=type.unaryExpression(//构建switch判断部分switch(+array[index++])operator='+',argument=type.memberExpression(object=array,property=type.updateExpression(operator='++',argument=index),computed=true),),cases=cases)//case节点]))//用分发器和while循环来替换原有的节点path.get('body').replaceWith(type.blockStatement(body=[dispenser,whileSta]))}})
思路这种加密与逐行加密区别不大,只是去掉了字符串加密函数,改用charCodeAt将字符串转到ASCII码,把解密函数换成String.fromCharCode,最后使用eval执行//代码逐行ASCII码混淆traverse(ast,{FunctionExpression(path){letblockStatement=path.node.bodyletstatement=blockStatement.body.map((v)=>{if(type.isReturnStatement(v)){returnv;//返回节点处理}v.leadingComments&&v.leadingComments[0].value.replace('','')=='encryptAscii'&&deletev.leadingComments;//删加密标识注释的兼容处理//判断是否是注释行if(!(v.trailingComments&&v.trailingComments[0].value.replace('','')=='encryptAscii')){returnv}deletev.trailingComments;//删除注释//遍历生成eval(String.fromCharCode())这种节点letcode=generator(v).code;letcodeAscaii=[].map.call(code,(v)=>{returntype.numericLiteral(v.charCodeAt(0))});//生成节点returntype.expressionStatement(expression=type.callExpression(callee=type.identifier('eval'),_arguments=[type.callExpression(callee=type.memberExpression(object=type.identifier(name='String'),property=type.identifier(name='fromCharCode')),_arguments=codeAscaii)]))})path.get('body').replaceWith(type.blockStatement(statement))//替换}})
特别说明:本文生成节点时,函数的使用对于Js来说是错误的(不能指定参数给值),这样写是方便看节点属性思路遍历FunctionExpression函数节点,然后把其中的body逐行加密,然后使用时解密,使用eval执行,但是大范围使用,特征太明显,所以可以在源代码中写上需要加密行的标上注释,就可以指定关键行,使用这种混淆方式letencFun=(str)=>{returnBuffer.from(str,'utf-8').toString('base64');}traverse(ast,{FunctionExpression(path){letblockStatement=path.node.bodyletbody=blockStatement.body.map((v)=>{if(type.isReturnStatement(v)){returnv;//返回节点处理}//遍历生成eval(stob('xxx'))这种节点letcode=generator(v).code;letcipherText=encFun(code);returntype.expressionStatement(expression=type.callExpression(callee=type.identifier(name='eval'),_arguments=[type.callExpression(callee=type.identifier('atob'),_arguments=[type.stringLiteral(cipherText)])]))})path.get('body').replaceWith(type.blockStatement(body=body))//替换}})指定行指定行就是判断这一行是否有指定的注释,然后再把注释删掉//指定行加密letencFun=(str)=>{returnBuffer.from(str,'utf-8').toString('base64');}traverse(ast,{FunctionExpression(path){letblockStatement=path.node.bodyletstatement=blockStatement.body.map((v)=>{if(type.isReturnStatement(v)){returnv;//返回节点处理}v.leadingComments&&v.leadingComments[0].value.replace('','')=='encrypt'&&deletev.leadingComments;//删加密标识注释的兼容处理//判断是否是注释行if(!(v.trailingComments&&v.trailingComments[0].value.replace('','')=='encrypt')){returnv}deletev.trailingComments;//删除注释//遍历生成eval(stob('xxx'))这种节点letcode=generator(v).code;letcipherText=encFun(code);returntype.expressionStatement(expression=type.callExpression(callee=type.identifier(name='eval'),_arguments=[type.callExpression(callee=type.identifier('atob'),_arguments=[type.stringLiteral(cipherText)])]))})path.get('body').replaceWith(type.blockStatement(statement))//替换}})
特别说明:本文生成节点时,函数的使用对于Js来说是错误的(不能指定参数给值),这样写是方便看节点属性二项式转函数花指令花指令用来更好的隐藏源代码的真实意图,还能增加代码量,增加分析难度类型一//源代码varnum=a+b;//变成functionxxx(c,d){returnc+d;}varnum=xxx(a,b)类型二//源代码varnum=add(a);//变成functionxxx(c,v){returnc(v);}varnum=xxx(add,v)类型一思路获取二项式的符号和左右部分,然后生成函数节点,把函数节点添加到当前代码块的最前面,然后把原始的二项式替换为调用表达式//二项式转花指令traverse(ast,{BinaryExpression(path){letoperator=path.node.operator;letleft_=path.node.left;//加个下滑线防止冲突letright_=path.node.right;//构造函数节点letfunName=path.scope.generateUidIdentifier('xxx');leta=type.identifier(name='a');letb=type.identifier(name='b');letfunc=type.functionDeclaration(id=funName,params=[a,b],body=type.blockStatement(body=[type.returnStatement(argument=type.binaryExpression(operator=operator,left=a,right=b))]))//把生成的函数节点添加到当前代码的最前面letblockStatement=path.findParent(function(p){returnp.isBlockStatement()})blockStatement.node.body.unshift(func);//替换原节点为调用表达式path.replaceWith(type.callExpression(callee=funName,_arguments=[left_,oright_]))}})类型二思路获取调用表达式的函数和参数,然后生成函数节点,把函数节点添加到当前代码块的最前面,然后把原始的调用表达式替换为新的调用表达式,只处理一层的fun('aa')不处理fun.fun('aa')例子functionadd(a,b){returna+b;}console.log(add(2,3))代码//调用表达式转花指令traverse(ast,{CallExpression(path){letcallee_=path.node.callee;letarguments_=path.node.arguments;if(!type.isIdentifier(callee_)){//只处理一层的`fun('aa')`不处理`fun.fun('aa')`return}//构造函数节点letfunName=path.scope.generateUidIdentifier('fff');letf=type.identifier(name='f')//第一个参数,是函数letparams_=[];//参数列表for(letarginarguments_){params_.push(name=path.scope.generateUidIdentifier('arg'))}letfunc=type.functionDeclaration(id=funName,params=[f].concat(params_),body=type.blockStatement(body=[type.returnStatement(type.callExpression(callee=f,_arguments=params_))]))//把生成的函数节点添加到当前代码的最前面letblockStatement=path.findParent(function(p){returnp.isBlockStatement()||p.isProgram()//兼容处理如何函数需要添加到最外面层})blockStatement.node.body.unshift(func);//替换原节点为调用表达式arguments_.unshift(callee_)path.replaceWith(type.callExpression(callee=funName,_arguments=arguments_))path.skip()}})
scope作用域scope提供了一些属性和方法使用的例子consta=1000;letb=2000;letobj={name:'haha',add:function(a){a=400;b=300;lete=700;functiondemo(){letb=600;}demo();returna+a+b+1000+obj.name}}obj.add(100)获取’标识符’作用域path.scope.block()可以获取标识符作用域,返回Node对象visitor={Identifier(path){if(path.node.name==='e'){//e的作用域就是整个函数console.log(generator(path.scope.block).code)}}}traverse(ast,visitor)/*function(a){a=400;b=300;lete=700;functiondemo(){letb=600;}demo();returna+a+b+1000+obj.name;}*/但有时候作用域输出的范围与实际不符合,就需要获取父节点的作用visitor={FunctionDeclaration(path){//函数声明只有demo()满足要求console.log(generator(path.scope.block).code)}}traverse(ast,visitor)/*functiondemo(){letb=600;}*/demo()的作用域实际上应该是整个add()的范围,这时就与实际输出不符合,就需要获取父节点的作用域visitor={FunctionDeclaration(path){console.log(generator(path.scope.parent.block).code)}}traverse(ast,visitor)/*function(a){a=400;b=300;lete=700;functiondemo(){letb=600;}demo();returna+a+b+1000+obj.name;}*/path.scope.getBinding获取标识符对应的绑定对象(Binding)visitor={FunctionDeclaration(path){letbinding=path.scope.getBinding('a')console.log(binding)}}traverse(ast,visitor)/*Binding{identifier:Node{type:"Identifier",start:83,end:84,loc:SourceLocation,name:"a"},//标识符的Node对象scope:Scope{uid:1,path:NodePath,block:Node,labels:Map(0),inited:true,...},//对应标识符的scope对象path:NodePath{contexts:Array(0),state:Object,opts:Object,_traverseFlags:0,skipKeys:null,...},//对应标识符的Path对象kind:"param",//该标识符类型这里表示是一个参数constantViolations:Array(1)[NodePath],//存放修改了该标识符的节点constant:false,//是否是常量referencePaths:Array(2)[NodePath,NodePath],//引用了该标识符的NodePath数组referenced:true,//该标识符是否被引用references:2,//该标识符的引用次数hasDeoptedValue:false,//hasValue:false,value:null}*/referencePaths&constantViolations这两个是Binding对象里的属性/*Binding{...referencePaths:Array(2)[NodePath,NodePath],//引用了该标识符的NodePath数组,没有引用时为空数组constantViolations:Array(1)[NodePath],//存放修改了该标识符的节点,没有被修改时为空数组...}*/遍历作用域有两种方式遍历作用域path.scope.traverse({})binding.scope.traverse({}):推荐//path.scope.traverse({})visitor={FunctionExpression(path){letbinding=path.scope.traverse({Identifier(path){console.log('')}})}}traverse(ast,visitor)//binding.scope.traverse({})推荐visitor={FunctionExpression(path){letbinding=path.scope.getBinding('a')binding.scope.traverse({Identifier(path){console.log('')}})}}traverse(ast,visitor)把例子函数中的a=400改成a=60visitor={FunctionExpression(path){letbinding=path.scope.getBinding('a')binding.scope.traverse(path.scope.block,{//遍历a的作用域AssignmentExpression(path){if(path.node.left.name==='a'){path.node.right=type.valueToNode(60)}}})}}traverse(ast,visitor)标识符重命名binding.scope.rename()该方法,会重命名所有引用了该标识符的地方visitor={FunctionExpression(path){letbinding=path.scope.getBinding('b')binding&&binding.scope.rename('b','xx')}}traverse(ast,visitor)/*consta=1000;letb=2000;letobj={name:'haha',add:function(xx){//a-->xxxx=400;//函数的参数a-->xxb=300;lete=700;functiondemo(){letb=600;}demo();returnxx+xx+b+1000+obj.name;}};obj.add(100);*/随机生成标识符path.scope.generateUidIdentifier('_0xoO').name该方法会生成一个字符串且不会重复可以用来实现简单的标识符混淆,也可以用来反混淆,使变量看起来好分析visitor={Identifier(path){path.scope.rename(path.node.name,path.scope.generateUidIdentifier('_0xoO').name)}}traverse(ast,visitor)/*const_0xoO=1000;let_0xoO15=2000;let_0xoO18={name:'haha',add:function(_0xoO14){_0xoO14=400;_0xoO15=300;let_0xoO9=700;function_0xoO12(){let_0xoO11=600;}_0xoO12();return_0xoO14+_0xoO14+_0xoO15+1000+_0xoO18.name;}};_0xoO18.add(100);*/scope其他方法path.scope.hasBinding('a')返回值是bool值,作用是查询该作用域是否有该标识符的绑定该方法可以用path.scope.getBinding('a')代替,只是该方法的返回值是undefined和Binding{}visitor={Identifier(path){console.log(path.scope.hasBinding('a'))}}traverse(ast,visitor)/*true...*/path.scope.getAllBindings()获取该节点的所有绑定,返回的对象以标识符名为属性名,对应的属性是Binding{..}visitor={Identifier(path){console.log(path.scope.getAllBindings())}}traverse(ast,visitor)/*Object{a:Binding{..},b:Binding{..},obj:Binding{..}}*/path.scope.hasReference('a')查询当前节点是否有标识符a的引用,返回值是true/false
Path对象Path&Node的区别Path是描述父子节点之间的链接,Path除了下面这些属性外,还包含添加、更新、移动、删除等方法Node对象是Path对象中的一个属性。path.node能取出当前遍历到的节点各种具体信息,不能使用Path对象的各种方法/*NodePath{contexts:Array(1)[TraversalContext],//上下文内容嵌套自己state:undefined,opts:Object{Identifier:Object,_exploded:true,_verified:true},_traverseFlags:0,skipKeys:null,parentPath:NodePath{contexts:Array(1),state:undefined,opts:Object,_traverseFlags:0,skipKeys:null,...},//父节点Path对象container:Node{type:"VariableDeclarator",start:4,end:162,loc:SourceLocation,id:Node,...},//容器里面是兄弟节点,有数组(有兄弟)和单个对象(没有兄弟)listKey:undefined,//有兄弟节点时,兄弟节点的容器的名字key:"id",//有兄弟节点时,代表兄弟容器索引,没有兄弟节点,代表对象属性名node:Node{type:"Identifier",start:4,end:7,loc:SourceLocation,name:"obj"},//Node对象type:"Identifier",parent:Node{type:"VariableDeclarator",start:4,end:162,loc:SourceLocation,id:Node,...},//父节点Node对象hub:undefined,data:null,context:TraversalContext{queue:Array(1),priorityQueue:Array(0),parentPath:NodePath,scope:Scope,state:undefined,...},scope:Scope{uid:0,path:NodePath,block:Node,labels:Map(0),inited:true,...},//作用域}*/Path对象中的方法获取当前节点为了获取到AST节点的属性值,需要先访问到该节点,然后用path.node.xxx(xxx相关属性)获取获取Node对象letvisitor={Identifier(path){console.log(path.node)//获取当前节点Node对象console.log(path.node.left)//获取Node节点具体属性值}}获取Path对象,但是有些属性值就没有必要包装operator、name…letvisitor={BinaryExpression(path){console.log(path.get('node'))//会把node包装成Path对象返回console.log(path.get('node.left'))//支持多级访问}}判断Path类型letvisitor={BinaryExpression(path){console.log(path.get('left').isIdentifier())console.log(path.get('right').isNumericLiteral({value:1000}))console.log(path.get('left').assertIdentifier())}}/*falsetrue报错*/节点转代码代码比较复杂,需要动态调试,就可以使用该方法,方便查看某一小段代码,就不需要到最后才全部再转换成代码,再看letvisitor={FunctionExpression(path){console.log(generator(path.node).code)console.log(path.toString())//因为Path对象,重写了toString()方法}}/*function(a,b){returna+b+1000;}function(a,b){returna*b+1000;}*/替换节点属性替换需要属性,需要符合实际逻辑,在允许的范围替换,不能随便替换,造成逻辑错误visitor={BinaryExpression(path){path.node.left=type.identifier(name="x")path.node.right=type.identifier(name="y")}}/*add:function(a,b){returnx+y;},mul:function(a,b){returnx+y;}*/替换节点replaceWith()使用一个节点替换一个节点,一对一的替换visitor={BinaryExpression(path){path.replaceWith(type.valueToNode('mimi'))}}/*add:function(a,b){return"mimi";},mul:function(a,b){return"mimi";}*/replaceWithMultiple()为多个节点替换一个节点两点特别说明:当替换的表达式替换后在一行时,最好使用type.expressionStatement()包裹一层新替换的节点traverse()也会去遍历,所以注意别陷入不合理的递归死循环遍历ReturnStatement()替换,替换里又有ReturnStatement(),陷入递归死循环,所以使用path.stop()停止visitor={ReturnStatement(path){path.replaceWithMultiple([type.expressionStatement(type.valueToNode('ZZ')),type.expressionStatement(type.valueToNode('XX')),type.returnStatement(type.valueToNode('YY')),])path.stop();}}traverse(ast,visitor)/*add:function(a,b){"ZZ";"XX";return"YY";},*/replaceInline(),接受一个参数,作用相对于是replaceWith()replaceWithMultiple()的综合当接受的参数是一个节点,就是replaceWith()当接受的参数是一个节点数组,就是replaceWithMultiple()添加有path.stop()的原因与上面一样visitor={StringLiteral(path){path.replaceInline(type.valueToNode('Hellemimi'))path.stop()},ReturnStatement(path){path.replaceInline([type.expressionStatement(type.valueToNode('ZZ')),type.expressionStatement(type.valueToNode('XX')),type.returnStatement(type.valueToNode('YY')),])path.stop()}}traverse(ast,visitor)replaceWithSourceString()接受字符串作为参数,然后解析到AST里面replaceWithSourceString()替换后的节点也会被解析,所以也需要path.stop()防止陷入递归死循环//把函数改为`闭包形式`visitor={ReturnStatement(path){letargumentPath=path.get('argument')argumentPath.replaceWithSourceString('function(){return'+argumentPath+'}'//字符串拼接会自动调用toString())path.stop()}}/*add:function(a,b){returnfunction(){returna+b+1000;};},*/删除节点visitor={EmptyStatement(path){//空语句也就是多余的分号path.remove()}}traverse(ast,visitor)插入节点插入兄弟节点insertAfter()当前节点之前,insertBefore()当前节点之后如果要操作ReturnStatement,可以先判断,直接return,注意该是用path.stop()是不行的visitor={ReturnStatement(path){path.insertAfter(type.valueToNode("XX"))path.insertBefore(type.valueToNode('MM'));}}traverse(ast,visitor)父级Path在Path对象中有两个属性parentPath、parent,其中parentPath是NodePath类型,是父级的Path对象,parent是父级的Node对象path.findParent&path.find参数都是一个函数,返回值是Path对象,没有找到返回null从当前节点,一直向上遍历,知道满足对应条件path.findParent()常用,查找范围不包含当前节点path.find()不常用,查找范围包含当前节点visitor={ReturnStatement(path){console.log(path.findParent((path)=>{returnpath.isObjectExpression()}))}}/*NodePath{...}*/path.getFunctionParent向上查找与当前节点最接近的父函数节点,返回值是Path对象visitor={ReturnStatement(path){console.log(path.getFunctionParent())}}traverse(ast,visitor)/*NodePath{...}NodePath{...}*/path.getStatementParent向上遍历找到父语句节点,如:return,if,whitch,while…visitor={ReturnStatement(path){console.log(path.getStatementParent())//会从当前节点开始//console.log(path.parentPath.getStatementParent())//取巧}}traverse(ast,visitor)其他方法替换path.parentPath.replaceWith()path.parentPath.replaceWithMultiple()path.parentPath.replaceInline()path.parentPath.replaceWithSourceString()删除path.parentPath.remove()同级节点了解同级节点,首要了解Path对象的这几个属性:container、listKey、keycontainer:有兄弟节点时,是兄弟节点数组,没有兄弟节点时就是当前节点Node对象listKey:有兄弟节点时,为容器名字,没有时是undefinedkey:有兄弟节点时,为当前节点,在容器中的索引,没有时,是当前节点属性名相关属性visitor={ReturnStatement(path){console.log(path.inList)//trueconsole.log(path.listKey)//bodyconsole.log(path.key)//0console.log(path.container)//[Node{...}]}}traverse(ast,visitor)获取兄弟节点path.getSibling参数是容器中节点的索引,visitor={ReturnStatement(path){console.log(path.getSibling(path.key))//Node{...}}}traverse(ast,visitor)网容器添加节点path.parentPath.unshiftContainer():在容器头部添加节点path.parentPath.pushContainer():在容器尾部添加节点添加之后的节点,也会再进行遍历,所以还是需要path.stop(),以防止进入递归死循环visitor={Property(path){path.parentPath.unshiftContainer(path.listKey,[type.objectProperty(key=type.identifier(name="unshift"),//第一个属性value=type.stringLiteral(value="OOO"))//第一个属性值)])path.parentPath.pushContainer(path.listKey,[type.objectProperty(key=type.identifier(name="push"),//第一个属性value=type.stringLiteral(value="MMM"))//第一个属性值)])path.stop()}}traverse(ast,visitor)
types组件types能用于节点类型的判断和节点的生成,但是主要还是用来节点的生成,因为节点类型的判断可以不用该组件,也能实现类型判断//其他类型也是一样type.isIdentifier(path.node)//相当于path.node.type==='Identifier'type.isIdentifier(path.node,{'name':'x'})//相当于path.node.type==='Identifier'&&path.node.name==='x'节点生成生成实例代码constfs=require('fs')consttype=require("@babel/types")constgenerator=require("@babel/generator").default//AST转换为代码letobjPro1=type.objectProperty(key=type.identifier(name="name"),//第一个属性value=type.stringLiteral(value="haha"))//第一个属性值letbojPro2=type.objectProperty(key=type.identifier(name="add"),value=type.functionExpression(//函数节点id=null,params=[type.identifier(name="a"),type.identifier(name="b")],//参数列表body=type.blockStatement(//函数体body=[type.returnStatement(//返回值argument=type.binaryExpression(//二项式operator="+",left=type.binaryExpression(operator="+",left=type.identifier(name="a"),right=type.identifier(name="b")),right=type.numericLiteral(value=1000)))])))letbojPro3=type.objectProperty(key=type.identifier(name="mul"),//第三个属性value=type.functionExpression(//第三个属性值id=null,params=[type.identifier(name="a"),type.identifier(name="b")],body=type.blockStatement(body=[type.returnStatement(argument=type.binaryExpression(operator="+",left=type.binaryExpression(operator="*",left=type.identifier(name="a"),right=type.identifier(name="b")),right=type.numericLiteral(value=1000)))])))letobj=type.objectExpression([objPro1,bojPro2,bojPro3])//变量初始化letletDec=type.variableDeclarator(type.identifier(name="obj"),init=obj)//变量名letlocalAst=type.variableDeclaration("let",[letDec])//声明变量//ast转化为代码letcode=generator(localAst).codefs.writeFile("./output/code.js",code,(err=>{}))上面使用到stringLiteral、numericLiteraltypes还提供了其他字面量,按照语法树来,都是差不多的declarefunctionstringLiteral(value:string):StringLiteral;declarefunctionnumericLiteral(value:number):NumericLiteral;declarefunctionnullLiteral():NullLiteral;declarefunctionbooleanLiteral(value:boolean):BooleanLiteral;declarefunctionregExpLiteral(pattern:string,flags?:string):RegExpLiteral;当界面量太多,太复杂的时候,Bable也提供了一个简便的方式valueToNode()//可以看出valueToNode可以很方便的生成其他类型exportfunctionvalueToNode(value:undefined):IdentifierexportfunctionvalueToNode(value:boolean):BooleanLiteralexportfunctionvalueToNode(value:null):NullLiteralexportfunctionvalueToNode(value:string):StringLiteralexportfunctionvalueToNode(value:number):NumericLiteral|BinaryExpression|UnaryExpressionexportfunctionvalueToNode(value:RegExp):RegExpLiteralexportfunctionvalueToNode(value:ReadonlyArray<undefined|boolean|null|string|number|RegExp|object>):ArrayExpressionexportfunctionvalueToNode(value:object):ObjectExpressionexportfunctionvalueToNode(value:undefined|boolean|null|string|number|RegExp|object):Expressionconsole.log(generator(type.valueToNode(123)).code)console.log(generator(type.valueToNode('mimi')).code)console.log(generator(type.valueToNode(undefined)).code)console.log(generator(type.valueToNode(null)).code)/*123"mimi"undefinednull*/
parse&generator组件parse作用是代码转化为AST结构解析网站:https://astexplorer.net/constparser=require("@babel/parser");//需要导入letast=parser.parse(js_code)//代码转化为ast结构,与网站上一样letast1=parser.parse(js_code,{sourceType:"module",//如果有importexport关键字,需要使用该参数})console.log(JSON.stringify(ast,null,2))generator作用是AST结构转化为代码letcode=generator(ast).codeletcode1=generator(ast,{retainLines:false,//默认false,是否输出与源代码相同的行号comments:false,//默认true,是否保留注释compact:true//默认false,是否压缩代码}).codetraverse&visitor组件traverse用来遍历AST结构的节点,简单点说就是把所有节点都运行一遍traverse使用的深度优先策略//导入consttraverse=require("@babel/traverse").default;//遍历节点constgenerator=require("@babel/generator").default//AST转换为代码//相关操作letvisitor={}visitor.FunctionExpression=function(path){console.log("mmmmm")}traverse(ast,visitor)/*源码有两个函数节点所以输出两次mmmmmmmm*/visitor三种写法按照自己的喜好选择,最常用的visitor2letvisitor1={FunctionExpression:function(path){console.log('...')}}letvisitor2={FunctionExpression(path){console.log('...')}}letvisitor3={FunctionExpression:{enter(path){console.log('进入->函数节点')},exit(path){console.log('退出<-函数节点')}}}traverse(ast,visitor3)visitor组合写法使用|组合不同的类型letvisitor1={'FunctionExpression|BinaryExpression':function(path){console.log('...')}}letvisitor2={'FunctionExpression|BinaryExpression'(path){console.log('...')}}letvisitor3={'FunctionExpression|BinaryExpression':{enter(path){console.log('进入->节点')},exit(path){console.log('退出<-节点')}}}traverse(ast,visitor3)使用多个函数处理节点,会按照函数顺序执行functionfun1(path){console.log('11')}functionfun2(path){console.log('22')}letvisitor={FunctionExpression:{enter:[fun1,fun2]}}traverse(ast,visitor)traverse并非必须从头遍历//修改函数第一个参数为x,并修改函数内所有用了该参数的地方constupdateParamNameVisitor={Identifier(path){if(path.node.name===this.paramName){path.node.name="x"}}}constvisitor={FunctionExpression(path){//遍历函数节点constparamName=path.node.params[0].name//内部循环path.traverse(updateParamNameVisitor,{paramName//向子循环传递参数})}}traverse(ast,visitor)
依赖安装npminstall--save-dev@babel/core@babel/cli@babel/preset-envnpminstall--save@babel/polyfill结构操作思想:读取代码转换为AST->对节点进行增删改查操作->保存结果constfs=require('fs');constparser=require("@babel/parser");//代码解析为ASTconsttraverse=require("@babel/traverse").default;//遍历节点consttype=require("@babel/types")//节点类型判断constgenerator=require("@babel/generator").default//AST转换为代码constjs_code=fs.readFileSync("./input",{encoding:"utf-8"})letast=parser.parse(js_code)//代码转化为ast//相关操作//ast转化为代码letcode=generator(ast).code//保存fs.writeFile("./output",code,(err=>{}))案例代码letobj={name:'haha',add:function(a,b){returna+b+1000},mul:function(a,b){returna*b+1000}}