前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >面试官: 你了解过Babel吗?写过Babel插件吗? 答: 没有。卒

面试官: 你了解过Babel吗?写过Babel插件吗? 答: 没有。卒

作者头像
落落落洛克
发布2021-08-25 17:15:13
4720
发布2021-08-25 17:15:13
举报
文章被收录于专栏:前端壹栈前端壹栈

欢迎加入技术交流群

也就前两天,面试大厂,其中有那么一个问题:

1.你了解过Babel吗?

了解过抽象语法树,又称AST,有学习过,也写过一个基于AST的乞丐版模板引擎,先是词法解析token,然后生产抽象语法树,然后更改抽象语法树,当然这是插件做的事情,最后根据新的AST生成代码。

2.写过Babel插件吗

没有,只是看过相关文档

3.如果让你写一个插件,你能写的出来吗?

啊,这,我试试!

遂卒....

开玩笑的,既然提到了,又没回答上来什么,哎哟我这暴脾气,一想到今晚就睡不着,连夜把它撸了。

那么我们来从零写个插件吧。

写一个预计算简单表达式的插件

预览

Before:

代码语言:javascript
复制
const result = 1 + 2 + 3 + 4 + 5;

After:

代码语言:javascript
复制
const result = 15;

以上的例子可能大家不会经常遇到,因为傻x才会这么写,但是有可能你会这么写

代码语言:javascript
复制
setTimeout(function(){
  // do something
}, 1000 * 2) // 插件要做的事,就是把 1000 * 2 替换成 2000

开工

在写代码之前,你需要明白Babel它的原理,简单点说:Babel解析成AST,然后插件更改AST,最后由Babel输出代码。

那么Babel的插件模块需要你暴露一个function,function内返回visitor。

代码语言:javascript
复制
module.export = function(babel){
  return {
    visitor:{
    }
  }
}

visitor是对各类型的AST节点做处理的地方,那么我们怎么知道Babel生成了的AST有哪些节点呢?

很简单,你可以把Babel转换的结果打印出来,或者这里有传送门: AST explorer:https://link.juejin.cn/?target=https%3A%2F%2Fastexplorer.net%2F

这里我们看到 const result = 1 + 2中的1 + 1是一个BinaryExpression节点,那么在visitor中,我们就处理这个节点

代码语言:javascript
复制
var babel = require('babel-core');
var t = require('babel-types');

const visitor = {
  BinaryExpression(path) {
    const node = path.node;
    let result;
    // 判断表达式两边,是否都是数字
    if (t.isNumericLiteral(node.left) && t.isNumericLiteral(node.right)) {
      // 根据不同的操作符作运算
      switch (node.operator) {
        case "+":
          result = node.left.value + node.right.value;
          break
        case "-":
          result = node.left.value - node.right.value;
          break;
        case "*":
          result =  node.left.value * node.right.value;
          break;
        case "/":
          result =  node.left.value / node.right.value;
          break;
        case "**":
          let i = node.right.value;
          while (--i) {
            result = result || node.left.value;
            result =  result * node.left.value;
          }
          break;
        default:
      }
    }

    // 如果上面的运算有结果的话
    if (result !== undefined) {
      // 把表达式节点替换成number字面量
      path.replaceWith(t.numericLiteral(result));
    }
  }
};

module.exports = function (babel) {
  return {
    visitor
  };
}

插件写好了,我们运行下插件试试

代码语言:javascript
复制
const babel = require("babel-core");

const result = babel.transform("const result = 1 + 2;",{
  plugins:[
    require("./index")
  ]
});

console.log(result.code); // const result = 3;

与预期一致,那么转换 const result = 1 + 2 + 3 + 4 + 5;呢?

结果是: const result = 3 + 3 + 4 + 5;

这就奇怪了,为什么只计算了1 + 2之后,就没有继续往下运算了?

我们看一下这个表达式的AST树

你会发现Babel解析成表达式里面再嵌套表达式。

代码语言:javascript
复制

表达式( 表达式( 表达式( 表达式(1 + 2) + 3) + 4) + 5)

而我们的判断条件并不符合所有的,只符合1 + 2

代码语言:javascript
复制
// 判断表达式两边,是否都是数字
if (t.isNumericLiteral(node.left) && t.isNumericLiteral(node.right)) {}

那么我们得改一改

第一次计算1 + 2之后,我们会得到这样的表达式

代码语言:javascript
复制
表达式( 表达式( 表达式(3 + 3) + 4) + 5)

其中 3 + 3又符合了我们的条件, 我们通过向上递归的方式遍历父级节点 又转换成这样:

代码语言:javascript
复制
表达式( 表达式(6 + 4) + 5)
表达式(10 + 5)
15
代码语言:javascript
复制
// 如果上面的运算有结果的话
if (result !== undefined) {
  // 把表达式节点替换成number字面量
  path.replaceWith(t.numericLiteral(result));

  let parentPath = path.parentPath;

  // 向上遍历父级节点
  parentPath && visitor.BinaryExpression.call(this, parentPath);
}

到这里,我们就得出了结果 const result = 15;

那么其他运算呢:

代码语言:javascript
复制
const result = 100 + 10 - 50 >>> const result = 60;
const result = (100 / 2) + 50 >>> const result = 100;
const result = (((100 / 2) + 50 * 2) / 50) ** 2 >>> const result = 9;

完结

到这里,已经向你大概的讲解了,如何编写一个Babel插件,再也不怕面试官问我答不出什么了哈...

你以为这就完了吗?

并没有

如果转换这样呢: const result = 0.1 + 0.2;

预期肯定是0.3, 但是实际上,Javascript有浮点计算误差,得出的结果是0.30000000000000004

那是不是这个插件就没卵用?

这就需要你去矫正浮点运算误差了,可以使用Big.js; 比如: result = node.left.value + node.right.value; 改成 result = +new Big(node.left.value).plus(node.right.value);你以为完了吗? 这个插件还可以做很多

代码语言:javascript
复制
比如: Math.PI * 2 >>> 6.283185307179586
比如: Math.pow(2, 2) >>> 4
...
...

优化

有旁友指出:

parentPath那一块可以换个实现方法。第一个binaryExpression真正被计算之后是会被替换成numericLiteral的。由于每个node都会被visit两次,所以在exit访问时,对于父节点而言两个子节点又同时是numericLiteral了,再次执行即可。也就是说,只要在enter和exit节点的时候,都执行一下上面替换节点的代码就行,无须手动遍历父节点去计算并替换~

代码语言:javascript
复制
BinaryExpression: {
 exit: path => {
   const node = path.node;
   const result = node.left.value + node.right.value
   path.replaceWith(t.numericLiteral(result));
 }
}

真是个不错的方法呢!

作者:Axetroy4281 链接:https://juejin.cn/post/6844903566809759758

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-08-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端壹栈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.你了解过Babel吗?
  • 2.写过Babel插件吗
  • 3.如果让你写一个插件,你能写的出来吗?
  • 开工
  • 完结
  • 优化
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档