前端AST

前端AST

定义:抽象语法树AST 树状形式呈现 每个节点都是源代码的一种结构

常见使用场景:

  • 代码语法检查
  • 代码风格检查
  • 代码风格格式化
  • 代码高亮
  • 代码错误提示
  • 代码自动补全

构建工具层面:

  • webpack plugin Loader
  • postcss
  • eslint代码检查内容

开发框架方面:

  • vue template 解析
  • react jsx 解析

怎么把vue或者react进行解析转化呢?

编译器:

定义:编译器是把一种语言转化为其他语言

前端:从一个比较高级的语言 -> 比较低级的语言

例如:

  • ES6 -> ES5
  • Less/sass ->css
  • ts -> js
  • 编译时 rax -> 小程序DSL

编译器转化基本思路:

  1. 词法分析: input -> tokenizer -> tokens
  • 输入的内容 转为token: input token: int main x = ;=>tokens
  • 例如:let x = 5 + 3;将源代码分解成 tokens:letx=5+3;

2.语法分析: tokens -> parser -> astast -> 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.代码生成

小结:

  1. 词法分析:input -> tokenizer -> tokens
  2. 语法分析:tokens -> parser -> ast
  3. 中间层代码转换:ast -> transformer ->new ast
  4. 目标代码: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
}

理应得到的是:

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;
};
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值