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

文章:http://www.javashuo.com/article/p-xwepwyup-hm.htmljavascript

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

  1. 你了解过Babel吗?

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

  1. 写过Babel插件吗

没有,只是看过相关文档git

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

应该能够吧...github

遂卒....web

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

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

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

预览

Before:post

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 来源:掘金 著做权归做者全部。商业转载请联系做者得到受权,非商业转载请注明出处。
相关文章
相关标签/搜索