• Babel(抽象语法树,又称AST)


    文章:https://juejin.im/post/5a9315e46fb9a0633a711f25

              https://github.com/jamiebuilds/babel-handbook/blob/master/translations/zh-Hans/plugin-handbook.md  

    1. 你了解过Babel吗?

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

    1. 写过Babel插件吗

    没有,只是看过相关文档

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

    应该可以吧...

    遂卒....

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

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

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

    预览

    Before:

    const result = 1 + 2 + 3 + 4 + 5;
    

    After:

    const result = 15;
    

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

    setTimeout(function(){
      // do something
    }, 1000 * 2) // 插件要做的事,就是把 1000 * 2 替换成 2000
    

    前提条件

    开工

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

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

    module.export = function(babel){
      return {
        visitor:{
        }
      }
    }
    

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

    很简单,你可以把Babel转换的结果打印出来,或者这里有传送门: AST explorer

    1

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

    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 = right;
              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
      };
    }
    

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

    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树

    2

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

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

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

        // 判断表达式两边,是否都是数字
        if (t.isNumericLiteral(node.left) && t.isNumericLiteral(node.right)) {}
    

    那么我们得改一改

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

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

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

        // 如果上面的运算有结果的话
        if (result !== undefined) {
          // 把表达式节点替换成number字面量
          path.replaceWith(t.numericLiteral(result));
    
          let parentPath = path.parentPath;
    
          // 向上遍历父级节点
          parentPath && visitor.BinaryExpression.call(this, parentPath);
        }
    

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

    那么其他运算呢:

    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);

    你以为完了吗? 这个插件还可以做很多

    比如: Math.PI * 2 >>> 6.283185307179586

    比如: Math.pow(2,2) >>> 4

    ...

    ...

    最后上项目地址: github.com/axetroy/bab…


    作者:Axetroy
    链接:https://juejin.im/post/5a9315e46fb9a0633a711f25
    来源:掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 相关阅读:
    String,StringBuffer和StringBuilder的异同
    博客迁移到reetsee.com
    一个好用的打印插件,功能强大
    html5中使用标签支持视频播放
    Extjs4 中在指定光标处插入值
    Javascript 创建对象方法的总结
    JS中的prototype
    在JS方法中返回多个值的三种方法
    JS ready和onload事件 比较分析
    JS中的“!!”
  • 原文地址:https://www.cnblogs.com/zhaobao1830/p/8479459.html
Copyright © 2020-2023  润新知