前端AST
定义:抽象语法树AST 树状形式呈现 每个节点都是源代码的一种结构
常见使用场景:
- 代码语法检查
- 代码风格检查
- 代码风格格式化
- 代码高亮
- 代码错误提示
- 代码自动补全
构建工具层面:
- webpack plugin Loader
- postcss
- eslint代码检查内容
开发框架方面:
- vue template 解析
- react jsx 解析
怎么把vue或者react进行解析转化呢?
编译器:
定义:编译器是把一种语言转化为其他语言
前端:从一个比较高级的语言 -> 比较低级的语言
例如:
- ES6 -> ES5
- Less/sass ->css
- ts -> js
- 编译时 rax -> 小程序DSL
编译器转化基本思路:
-
词法分析:
input -> tokenizer -> tokens- 输入的内容 转为token: input token: int main x = ;=>tokens
- 例如:
let x = 5 + 3;将源代码分解成 tokens:let,x,=,5,+,3,;
-
语法分析:
tokens -> parser -> ast、ast -> transformer ->new ast-
根据 JavaScript 的语法规则,验证这些 tokens 是否构成合法的表达式。
-
构建抽象语法树(AST)
-
// let x = 5 + 3;在语法分析的结果 Program └── ExpressionStatement └── VariableDeclaration ├── kind: "let" └── declarations └── VariableDeclarator ├── id: Identifier (x) └── init: BinaryExpression ├── left: Literal (5) ├── operator: "+" └── right: Literal (3)
-
ES6代码转换ES5过程:
ES6代码 -> @babel/paresr -> AST -> @babel/traverse -> new AST -> @babel/generator -> es5代码
3.代码转换
4.代码生成
小结:
- 词法分析:
input -> tokenizer -> tokens - 语法分析:
tokens -> parser -> ast - 中间层代码转换:
ast -> transformer ->new ast - 目标代码:
new ast -> generator -> output
babel
- @babel/parse 生成AST
- babel/traverse 接收AST 遍历 对节点处理
- babbel/generator 生成最终的AST
- babel/type 对节点进行校验 构建相关节点 改变node内容
- @babel/core 核心的api都在core中,并提供插件功能
小实战:
1.将var转为let
//ast.js
const parser = require('@babel/parser');
const traverse = require('@babel/traverse');
const generator = require('@babel/generator');
const transToLet = (code) => {
const ast = parser.parse(code);
// 访问者对象
const visitor = {
// 遍历声明表达式
// 可以直接通过节点的类型操作AST节点。
VariableDeclaration(path) {
// 替换
if (path.node.kind === 'var') {
path.node.kind = 'let';
}
},
};
traverse.default(ast, visitor);
// 生成代码
const newCode = generator.default(ast, {}, code).code;
return newCode;
};
const code = `const a = 1
var b = 2
let c = 3`;
console.log(transToLet(code));
直接使用node执行ast.js得到
const a = 1;
let b = 2;
let c = 3;
2.将箭头函数降级为es5
第一步:
const core = require('@babel/core')
// const arrowFunctionExpression = require('babel-plugin-transform-es2015-arrow-functions')
let sourceCode = `const sum = (a,b)=> a+b`;
///箭头函数插件
const arrowFunctionPlugin = {
visitor:{
ArrowFunctionExpression(path){
// path 就是当前arrowfunction节点
path.node.type = 'FunctionExpression';
}
}
}
let targetCode = core.transform(sourceCode,{
plugins:[arrowFunctionPlugin]
})
console.log(targetCode.code)
//const sum = function (a, b) a + b;
结果如下:
const sum = function (a, b) a + b;
这样会发现没有{}并且也没有return
接着需要对缺少的{}和return进行实现:
在网站https://astexplorer.net/#中:
{}:使用BlockStatementapi生成return:使用ReturnStatementapi生成
const core = require('@babel/core')
// 引入types对节点进行校验
const types = require('@babel/types')
let sourceCode = `const sum = (a,b)=> a+b`;
///箭头函数插件
const arrowFunctionPlugin = {
visitor:{
ArrowFunctionExpression(path){
const {node} = path;
// path 就是当前arrowFunction节点
path.node.type = 'FunctionExpression';
// 判断如果不是块级语句,则将箭头函数体包裹在块级语句中
if(!types.isBlockStatement(node.body)){
node.body = types.blockStatement([types.returnStatement(node.body)])
}
}
}
}
let targetCode = core.transform(sourceCode,{
plugins:[arrowFunctionPlugin]
})
console.log(targetCode.code)
此时运行后结果:
const sum = function (a, b) {
return a + b;
};
以为到这里就结束了么?嘿嘿,那你想的就太简单了,别忘了,箭头函数中的this的指向问题,
使用const arrowFunctionExpression = require('babel-plugin-transform-es2015-arrow-functions')插件去编译下面的代码:
const sum = (a,b)=>{
console.log(this)
return a + b
}
理应得到的是:
var _this = this;
const sum = function(a,b){
console.log(_this)
return a+b
}
但是使用我们的插件得到的却是:
const sum =function (a,b){
console.log(this)
return a + b
}
这显然不对
那么如何处理this呢?
整体思路如下:
- 第一步:找到当前箭头函数要使用哪个作用域内的
this,暂时称为父作用域 - 第二步:往父作用域中加入
_this变量,也就是添加语句:var _this = this - 第三步:找出当前箭头函数内所有用到
this的地方 - 第四步:将当前箭头函数中的
this,统一替换成_this
具体思路:从当前节点开始向上查找,直到找到一个不是箭头函数的函数,最后还找不到那就是根节点。
新增hoistFunctionEnvironment函数
const core = require('@babel/core');
// 引入types对节点进行校验
const types = require('@babel/types');
let sourceCode = `const sum = (a,b)=>{console.log(this);return a+b}`
//处理箭头函数this指向问题
// 具体思路:`从当前节点开始向上查找,直到找到一个不是箭头函数的函数,最后还找不到那就是根节点`。
// 新增`hoistFunctionExpression`函数
function hoistFunctionExpression(path) {
const thisEnv = path.findParent((parent) => {
// 找到不是一个箭头函数的父级,实在没有的话,就用根节点
return (
(parent.isFunction() && !parent.isArrowFunctionExpression()) ||
parent.isProgram()
);
});
//第二步 父级作用域中添加_this变量
// 这段代码的作用是在当前环境(thisEnv)的作用域中添加一个新的变量绑定,变量名为 _this,其值为 this 表达式。
thisEnv.scope.push({
id: types.identifier('_this'),
init: types.thisExpression(),
});
//第三步 找到箭头函数中所有用到this的地方
let thisPaths = [];
//对当前节点进行一个遍历,并将用到this的节点放入thisPaths中
path.traverse({
ThisExpression(thisPath){
thisPaths.push(thisPath)
}
})
// 第四步 遍历 替换this为_this
thisPaths.forEach(thisPath=>{
//创建一个节点_this去替换this
thisPath.replaceWith(types.identifier('_this'))
})
}
///箭头函数插件
const arrowFunctionPlugin = {
visitor: {
ArrowFunctionExpression(path) {
const { node } = path;
hoistFunctionExpression(path);
// path 就是当前arrowFunction节点
path.node.type = 'FunctionExpression';
// 判断如果不是块级语句,则将箭头函数体包裹在块级语句中
if (!types.isBlockStatement(node.body)) {
node.body = types.blockStatement([types.returnStatement(node.body)]);
}
},
},
};
let targetCode = core.transform(sourceCode, {
plugins: [arrowFunctionPlugin],
});
console.log(targetCode.code);
执行后得到如下结果:
var _this = this;
const sum = function (a, b) {
console.log(_this);
return a + b;
};