当前位置: 首页 > Web前端 > JavaScript

AST抽象语法树

时间:2023-03-27 18:34:56 JavaScript

作为一个前端同学,你是否知道AST是什么,完全不影响你在工作中使用它。我们项目中平时使用的less、babel、eslint、代码压缩、JavaScript代码都可以在浏览器中运行,都是基于AST的。了解了关于AST的知识之后,你也可以自己折腾出点东西来,为单调枯燥的工作找点乐子。什么是ASTAST(AbstractSyntaxTree),中文叫抽象语法树,是对源代码语法结构的抽象表示。它以树的形式表示编程语言的语法结构,树上的每个节点代表源代码中的一个结构。语法之所以“抽象”,是因为这里的语法并没有表达出真正语法中出现的每一个细节。例如,嵌套括号隐含在树结构中,不以节点的形式呈现;而像if-condition-then这样的条件跳转语句可以用具有三个分支的节点来表示。(以上概念来自维基百科)。JavaScriptAST转换工具对于JavaScript,可以通过JSParser将JS代码转换为AST。目前比较常见的JSParsers有:esprima(流行库)Babylon(用于babel)acorn(用于webpack)espree(源自acorn,用于eslint)astexplorer(在线生成工具,可选择不同的JSParserreal-timeviewing)本文的例子都是使用esprima实现的。如何将代码转换成AST在将代码转换成AST的过程中,有两个重要的阶段:词法分析(LexicalAnalysis)和句法分析(SyntaxAnalysis)。词法分析,也称为分词,是将字符串形式的代码转换为标记序列的过程。这里的token是一个字符串,是源代码的最小单位,类似于英文中的单词。词法分析也可以理解为将英文字母组合成单词的过程。词法分析时不关心词与词之间的关系。例如:在词法分析过程中可以将括号标记为token,但不检查括号是否匹配。JavaScript中的token主要有以下几种类型:关键字:var,let,const等标识符:不带引号的连续字符,可能是一个变量,if,else,或者true,false这些内置的关键字常量运算符:+、-、*、/等数字:十六进制、十进制、八进制等字符串,科学表达式:变量值等空格:连续空格、换行、缩进等注意:行注释或块注释是不可分割的最小语法单位。标点符号:花括号、圆括号、分号、冒号等。以下是esprima词法分析后consta='helloworld'生成的token。[{“类型”:“关键字”,“值”:“常量”},{“类型”:“标识符”,“值”:“a”},{“类型”:“标点符号”,“值”:"="},{"type":"String","value":"'helloworld'"}]语法分析也称为解析器,将词法分析产生的token按照给定的语法形式进行转换。成为AST的过程。即单词组合成句子的过程。转换过程中会校验语法,如果语法错误会抛出语法错误。上面的consta='helloworld'语法分析后生成的AST如下:type":"VariableDeclarator","id":{"type":"Identifier","name":"a"},"init":{"type":"Literal","value":"helloworld","raw":"'helloworld'"}}],"kind":"const"}],"sourceType":"script"}得到AST之后,我们就可以分析AST,自己做一些这个基础很重要。例如,最简单的方法是将代码中的某个变量替换为另一个名称。练习现在让我们用变量b替换上面代码中定义的变量a。要实现这个需求,我们需要将源代码转换成AST,然后在此基础上进行一些操作,改变树的内容,再将AST转换成目标代码。即要经过解析->转换-??>生成的过程。首先,我们需要分析一下源代码生成的AST和目标代码生成的AST具体有什么区别。这是constb='helloworld'生成的AST:,"id":{"type":"Identifier","name":"b"//这里不同},"init":{"type":"Literal","value":"helloworld","raw":"'helloworld'"}}],"kind":"const"}],"sourceType":"script"}通过对比分析发现,唯一不同的是name属性的值类型为Identifier的id不同。接下来,我们就可以通过修改AST来实现我们的需求了。我们需要安装两个包estraverse(遍历AST)和escodegen(从AST生成JS)。constesprima=require('esprima');constestraverse=require('estraverse');constescodegen=require('escodegen');constprogram="consta='helloworld'";constASTree=esprima.parseScript(程序);estraverse.traverse(ASTree,{enter(node){changeAToB(node);}});constASTreeAfterChange=escodegen.generate(tree);console.log(ASTreeAfterChange);//constb='helloworld'functionchangeAToB(node){if(node.type==='Identifier'){node.name='b';}}看看是否容易实现。掌握了AST的知识之后,我们可以做很多事情,各种babel插件也是这样生成的,只是使用的库不同而已。如何实现一个babel插件可以参考Babel插件官方手册参考文章【Whatyoushouldknow】抽象语法树AST平庸前端码农改造——ASTBabel插件手册