<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          面試官: 你了解過Babel嗎?寫過Babel插件嗎? 答: 沒有。卒

          共 6678字,需瀏覽 14分鐘

           ·

          2021-08-23 00:22

          歡迎加入技術交流群

          也就前兩天,面試大廠,其中有那么一個問題:

          1.你了解過Babel嗎?

          了解過抽象語法樹,又稱AST,有學習過,也寫過一個基于AST的乞丐版模板引擎,先是詞法解析token,然后生產(chǎn)抽象語法樹,然后更改抽象語法樹,當然這是插件做的事情,最后根據(jù)新的AST生成代碼。

          2.寫過Babel插件嗎

          沒有,只是看過相關文檔

          3.如果讓你寫一個插件,你能寫的出來嗎?

          啊,這,我試試!

          遂卒....

          開玩笑的,既然提到了,又沒回答上來什么,哎喲我這暴脾氣,一想到今晚就睡不著,連夜把它擼了。

          那么我們來從零寫個插件吧。

          寫一個預計算簡單表達式的插件

          預覽

          Before:

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

          After:

          const result = 15;

          以上的例子可能大家不會經(jīng)常遇到,因為傻x才會這么寫,但是有可能你會這么寫

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

          開工

          在寫代碼之前,你需要明白Babel它的原理,簡單點說:Babel解析成AST,然后插件更改AST,最后由Babel輸出代碼。

          那么Babel的插件模塊需要你暴露一個function,function內(nèi)返回visitor。

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

          visitor是對各類型的AST節(jié)點做處理的地方,那么我們怎么知道Babel生成了的AST有哪些節(jié)點呢?

          很簡單,你可以把Babel轉換的結果打印出來,或者這里有傳送門: AST explorer:https://link.juejin.cn/?target=https%3A%2F%2Fastexplorer.net%2F

          這里我們看到 const result = 1 + 2中的1 + 1是一個BinaryExpression節(jié)點,那么在visitor中,我們就處理這個節(jié)點

          var babel = require('babel-core');
          var t = require('babel-types');

          const visitor = {
            BinaryExpression(path) {
              const node = path.node;
              let result;
              // 判斷表達式兩邊,是否都是數(shù)字
              if (t.isNumericLiteral(node.left) && t.isNumericLiteral(node.right)) {
                // 根據(jù)不同的操作符作運算
                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) {
                // 把表達式節(jié)點替換成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之后,就沒有繼續(xù)往下運算了?

          我們看一下這個表達式的AST樹

          你會發(fā)現(xiàn)Babel解析成表達式里面再嵌套表達式。


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

          而我們的判斷條件并不符合所有的,只符合1 + 2

          // 判斷表達式兩邊,是否都是數(shù)字
          if (t.isNumericLiteral(node.left) && t.isNumericLiteral(node.right)) {}

          那么我們得改一改

          第一次計算1 + 2之后,我們會得到這樣的表達式

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

          其中 3 + 3又符合了我們的條件, 我們通過向上遞歸的方式遍歷父級節(jié)點 又轉換成這樣:

          表達式( 表達式(6 + 4) + 5)
          表達式(10 + 5)
          15
          // 如果上面的運算有結果的話
          if (result !== undefined) {
            // 把表達式節(jié)點替換成number字面量
            path.replaceWith(t.numericLiteral(result));

            let parentPath = path.parentPath;

            // 向上遍歷父級節(jié)點
            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;

          完結

          到這里,已經(jīng)向你大概的講解了,如何編寫一個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
          ...
          ...

          優(yōu)化

          有旁友指出:

          parentPath那一塊可以換個實現(xiàn)方法。第一個binaryExpression真正被計算之后是會被替換成numericLiteral的。由于每個node都會被visit兩次,所以在exit訪問時,對于父節(jié)點而言兩個子節(jié)點又同時是numericLiteral了,再次執(zhí)行即可。也就是說,只要在enter和exit節(jié)點的時候,都執(zhí)行一下上面替換節(jié)點的代碼就行,無須手動遍歷父節(jié)點去計算并替換~

          BinaryExpression: {
           exitpath => {
             const node = path.node;
             const result = node.left.value + node.right.value
             path.replaceWith(t.numericLiteral(result));
           }
          }

          真是個不錯的方法呢!

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

          瀏覽 37
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产娇小13videos糟蹋 | 撸一撸日一日 | 黄色片免费视频草逼草逼草逼草逼草逼 | 91视频久久久久久久久久久久 | 亚洲国产精品视频免费看 |