前言我们在写业务代码的时候,可能很少有人会用到AST,以至于大部分同学对AST了解不多。有的同学以前学过,但是如果不去实践,过一段时间就会忘记得差不多了。看到这里,你会发现原来是你。听说现在写文章要编故事,还要时不时点个表情包。这是真的?作为公司最重要的前端,我是不会放过的。本文将通过以下几个方面来学习AST1.基本了解什么是AST,AST有什么以及如何使用AST生成2.一个实用的小例子去掉调试器,修改函数中执行的console.log参数3.先总结一下什么是AST的基础知识贴上官方解释在计算机科学中,抽象语法树(abstractsyntaxtree或缩写为AST),或语法树(syntaxtree),是对抽象语法的树状表示源代码结构,特指编程语言的源代码。为了让大家更容易理解抽象语法树,我们来看一个具体的例子。vartree='thisistree'js源码会转化为如下抽象语法树{"type":"Program","start":0,"end":25,"body":[{"type":"VariableDeclaration","start":0,"end":25,"declarations":[{"type":"VariableDeclarator","start":4,"end":25,"id":{"type":"标识符”,“开始”:4,“结束”:8,“名称”:“树”},“初始化”:{“类型”:“文字”,“开始”:11,“结束”:25,“value":"thisistree","raw":"'thisistree'"}}],"kind":"var"}],"sourceType":"module"}可以看到一个语句是由几个词法单元组成的.这个令牌就像26个字母。创造几十万个单词,通过不同单词的组合,写出不同内容的文章。关于词法单元,可以点击查看AST对象文档或者参考文章前端基础知识-JavaScript抽象语法树AST列出了语法树节点和解释。推荐一个工具在线ast转换器。你可以在这个网站上自己尝试下转换。点击语句中的单词,会选中右侧的抽象语法树节点,如下图:AST有什么问题?使用IDE错误提示、代码格式化、代码高亮、代码自动补全等JSLint、JSHint用于代码错误或样式检查等webpack、rollup用于代码打包等将CoffeeScript、TypeScript、JSX等转换为原生Javascript。vue模板编译,react模板编译如何生成AST看到这里,你应该已经知道抽象语法树长什么样了。那么AST是如何生成的呢?AST的整个解析过程分为两步词法分析:扫描输入的源码字符串,生成一系列的token。这些token包括数字、标点符号、运算符等。词法单元是独立的,即现阶段我们不关心每一行代码是如何组合的。语法分析:建立并分析语法单元之间的关系还是以上面的vartree='thisistree'为例,形式化地理解词法分析。首先进行词法分析,扫描输入的源代码字符串,生成一系列词法单元(token)。这些词法单元包括数字、标点符号、运算符等。语法分析语法分析阶段会将上一阶段生成的token列表转化为AST如下图(我去掉了start和end字段,没有在乎它)非正式理解郑重声明:我周,很少通过中文,只要我能理解大概的意思。例如:“这是一头猪。”词法分析首先经过词法分析,扫描输入的源代码字符串,生成一系列词法单元(token)。这些词法单元包括数字、标点符号、运算符等。语法分析语法分析阶段会将前一阶段生成的token列表转换为如下图所示的ASTJsParserJavaScriptParser,它将js源代码转换为抽象语法树解析器。acornesprimatraceur@babel/parser实用小例子示例1:进入调试器源码:functionfn(){console.log('debugger')debugger;}根据我们之前学习的知识点,我们先想象一下如何去掉这个debugger首先将源代码转换成AST,遍历**AST**上的节点,找到**debugger**节点,删除转换后的AST生成JS代码。将源码复制到在线ast转换器中,点击左侧区域的Thedebugger,可以看到左侧的debugger节点被选中。所以只需要删除图中选中的debuggerabstractsyntaxtree节点即可。这个例子比较简单,直接上代码。在这个例子中,我使用了@babel/parser、@babel/traverse、@babel/generator,它们的功能分别是解析、转换和生成。constparser=require('@babel/parser');consttraverse=require("@babel/traverse");constgenerator=require("@babel/generator");//源码constcode=`functionfn(){console.log('debugger')debugger;}`;//1.源码解析成astconstast=parser.parse(code);//2。constvisitor={//traverse的转换会遍历树节点,只要节点的类型在visitor对象中出现change调用方法DebuggerStatement(path){//删除抽象语法树节点path.remove();}}traverse.default(ast,visitor);//3.生成constresult=generator.default(ast,{},code);console.log(result.code)//4.日志输出//functionfn(){//console.log('debugger');//}babel的核心逻辑处理在visitor中。traverse会遍历树节点,只要节点的类型出现在visitor对象中,就会调用该类型对应的方法,并在方法中调用path.remove()删除当前节点。demo中使用的路径部分API可以参考babel-handbook。示例2:修改函数中执行的console.log参数。有时候我们登录函数,但是想在控制台直观的看到是哪个函数登录了。这时候我们就可以使用AST来解析、转换并生成最终想要的代码。源码:functionfunA(){console.log(1)}//转换为functionfunA(){console.log('fromfunctionfunA:',1)}在编码之前,我们先梳理一下思路,不迟开始。这时候就需要借助在线的ast转换器进行分析了。通过工具发现,如果要实现这种情况,只需要在参数前面插入一个segment节点即可。这里和例1一样,先梳理一下思路,使用@babel/parser将源码解析成ast。监听@babel/traverse,遍历到CallExpression。触发后判断如果执行方式为console.log,unshiftaStringLiteralinargumentstoconvert下面将js代码解析成ast的ast生成代码与debugger示例一致,这里不再赘述。首先监听CallExpression,遍历constvisitor={CallExpression(path){//console.log(path)}},观察在线ast转换器解析出的树。我们只需要判断path的callee中对象console和property是否存在即可。您可以将StringLiteral取消转换为当前路径的参数。这里的types对象使用了一个新的包@babel/types来确定类型。上面用到的isMemberExpression、isIdentifier、getFunctionParent、stringLiteral都可以在babel-handbook文档中找到,本文不再赘述。constvisitor={//遍历到CallExpression时,触发CallExpression(path){constcallee=path.node.callee;//判断当前执行的函数是否为组合表达式if(types.isMemberExpression(callee)){const{object,property}=callee;if(types.isIdentifier(object,{name:'console'})&&types.isIdentifier(property,{name:'log'})){//找到最近的父函数或程序constparent=路径。getFunctionParent();constparentFunName=parent.node.id.name;path.node.arguments.unshift(types.stringLiteral(`fromfunction${parentFunName}`))}}}}我在日常工作中很少使用AST,因此大多数学生对AST了解不多。但是理解AST可以帮助我们更好地理解开发工具和编译器的原理,产生提高代码效率的工具。还记得在之前的前端团队遇到过一个问题。我们的项目是SSR项目,服务端执行的生命周期不允许只有客户端才能执行的代码。但是团队成员有时会无意中写,导致服务器端渲染降级。在学习AST之前,为了解决这个问题,我写了一个loader来通过正则化来匹配验证。当时真的快把我逼死了,正则化需要适应各种场景。后来学了AST之后,写了一个eslint插件实现客户端代码校验。参考babel-handbook(https://github.com/jamiebuilds/babel-handbook/blob/master/translations/zh-Hans/plugin-handbook.md)深入Babel,这个就够了(https://juejin.im/post/6844903746804137991)前端高级基础-JavaScript抽象语法树AST(https://juejin.cn/post/6844903798347939853#heading-12)
