当前位置: 首页 > 科技观察

采访者:你听说过巴别塔吗?写过Babel插件吗?答:没有_0

时间:2023-03-18 20:55:06 科技观察

面试大厂,有这样一个问题:1.你了解过Babel吗?学习过抽象语法树,也就是AST,我研究过,也写过一个基于AST的乞丐版模板引擎。首先对token进行词法解析,然后生成抽象语法树,再对抽象语法树进行修改。当然,这是插件的作用,最后根据新的AST生成代码。2.你写过Babel插件吗?没有,我只是看了相关文件。3.如果让你写插件,你能写吗?啊,来,我试试!隋祖……开玩笑的,既然提了,我什么都不回答,哎哟,脾气不好,想想今晚就睡不着了,打了一晚上耳光。所以让我们从头开始写一个插件。写一个预计算简单表达式的插件预览Before:constresult=1+2+3+4+5;之后:常量结果=15;上面的例子可能不会经常遇到,因为傻x会这样写,但是有可能你会这样写setTimeout(function(){//dosomething},1000*2)//插件需要做的是将1000*2替换为2000以开始工作。在写代码之前,需要先了解Babel的原理,简单来说就是:Babel将其解析成AST,然后插件对AST进行修改,最后Babel输出代码。然后Babel的插件模块需要你暴露一个函数,它返回一个访问者。module.export=function(babel){return{visitor:{}}}visitor是处理各种类型AST节点的地方,那么我们怎么知道Babel生成的AST中有哪些节点呢?很简单,打印出babel转换的结果即可,或者这里是传送门:ASTexplorer:https://astexplorer.net/这里我们看到constresult=1+2中的1+1是一个BinaryExpression节点,那么在访问者中,我们处理这个节点varbabel=require('babel-core');vart=require('babel-types');constvisitor={BinaryExpression(path){constnode=path.node;让结果;//判断表达式两边是否都是数字if(t.isNumericLiteral(node.left)&&t.isNumericLiteral(node.right)){//根据不同的运算符进行操作switch(node.operator){case"+":结果=node.left.value+node.right.value;breakcase"-":result=node.left.value-node.right.value;休息;case"*":result=node.left.value*node.right.value;休息;case"/":result=node.left.value/node.right.value;休息;case"**":让i=node.right.value;(--i){结果=结果||节点.left.value;结果=结果*node.left.value;}休息;default:}}//如果上面的操作有结果if(result!==undefined){//用数字文字替换表达式节点path.replaceWith(t.numericLiteral(result));}}};module.exports=function(babel){return{visitor};}插件写好了,我们运行一下插件试试constbabel=require("babel-core");constresult=babel.transform("const结果=1+2;",{插件:[require("./index")]});控制台。日志(结果。代码);//常量结果=3;符合预期,那么转换constresult=1+2+3+4+5;呢?结果是:constresult=3+3+4+5;which很奇怪,为什么只计算了1+2就没有继续计算呢?我们看一下这个表达式的AST树,你会发现Babel把它解析成一个表达式,然后嵌套这个表达式表达式(expression(expression(expression(1+2)+3)+4)+5)而我们的判断条件并不全部满足,只满足1+2//判断表达式两边是否都是一个数if(t.isNumericLiteral(node.left)&&t.isNumericLiteral(node.right)){}那么我们要改一下1+2的第一次计算之后,我们会得到这样一个表达式expression(expression公式(expression(3+3)+4)+5)其中3+3满足我们的条件,我们向上递归遍历父节点,转化为:expression(expression(6+4)+5)Expression(10+5)15//如果上面的操作有结果if(result!==undefined){//用数字文字替换表达式节点path.replaceWith(t.numericLiteral(result));让parentPath=path.parentPath;//向上遍历父节点parentPath&&visitor.BinaryExpression.call(this,parentPath);}在这里,我们得到结果constresult=15;其他操作呢:constresult=100+10-50>>>constresult=60;常量结果=(100/2)+50>>>常量结果=100;常量结果=(((100/2)+50*2)/50)**2>>>常量结果=9;到这里结束,我已经给大家讲解了如何写一个Babel插件,再也不怕面试官问我答不上来了。。。你以为这就结束了吗?不行,如果转换成这样:constresult=0.1+0.2;期望值肯定是0.3,但实际上Javascript有浮点计算错误,结果是0.30000000000000004。这个插件没用?这个需要你修正浮点计算错误,可以使用Big.js;例如:result=node.left.value+node.right.value;更改为result=+newBig(node.left.value).plus(node.right.value);你觉得结束了吗?这个插件还可以做很多如:Math.PI*2>>>6.283185307179586如:Math.pow(2,2)>>>4...有朋友在优化中指出:parentPath可以实现在一种不同的方式。第一个binaryExpression为true计算后,将替换为numericLiteral。由于每个节点都会被访问两次,当退出访问时,父节点的两个子节点同时为numericLiteral,重新执行即可。也就是说只要在进入和退出节点时执行上面的代码替换节点即可,不需要手动遍历父节点进行计算替换~BinaryExpression:{exit:path=>{constnode=path.node;constresult=node.left.value+node.right.valuepath.replaceWith(t.numericLiteral(result));}}真是个好方法!