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

          前端應(yīng)該掌握的編譯基礎(chǔ)(基于 babel)

          共 22648字,需瀏覽 46分鐘

           ·

          2021-07-05 08:42

          • 作者:陳大魚頭
          • github: KRISACHAN

          開(kāi)發(fā)息息相關(guān)

          雖然 Babel 團(tuán)隊(duì)在各種哭窮,但是 Babel 始終是我們前端在開(kāi)發(fā)中不可或缺的重要工具。 雖然我們只是 API 調(diào)用工,但是多了解一些總是會(huì)有好處的嘛 ??????

          什么是編譯器?

          編譯器(compiler)是一種計(jì)算機(jī)程序,它會(huì)將某種編程語(yǔ)言寫成的源代碼(原始語(yǔ)言)轉(zhuǎn)換成另一種編程語(yǔ)言(目標(biāo)語(yǔ)言)。

          源代碼(source code)→ 預(yù)處理器(preprocessor)→ 編譯器(compiler)→ 匯編程序(assembler)→ 目標(biāo)代碼(object code)→ 鏈接器(linker)→ 可執(zhí)行文件(executables),最后打包好的文件就可以給電腦去判讀運(yùn)行了。

          什么是解釋器?

          解釋器(英語(yǔ):interpreter),是一種計(jì)算機(jī)程序,能夠把解釋型語(yǔ)言解釋執(zhí)行。解釋器就像一位“中間人”。解釋器邊解釋邊執(zhí)行,因此依賴于解釋器的程序運(yùn)行速度比較緩慢。解釋器的好處是它不需要重新編譯整個(gè)程序,從而減輕了每次程序更新后編譯的負(fù)擔(dān)。相對(duì)的編譯器一次性將所有源代碼編譯成二進(jìn)制文件,執(zhí)行時(shí)無(wú)需依賴編譯器或其他額外的程序。

          跟編譯器的區(qū)別就是一個(gè)是邊編譯邊執(zhí)行,一個(gè)是編譯完才執(zhí)行。

          高級(jí)語(yǔ)言編譯器步驟

          1. 輸入源程序字符流
          2. 詞法分析
          3. 語(yǔ)法分析
          4. 語(yǔ)義分析
          5. 中間代碼生成
          6. 機(jī)器無(wú)關(guān)代碼優(yōu)化
          7. 代碼生成
          8. 機(jī)器相關(guān)代碼優(yōu)化
          9. 目標(biāo)代碼生成

          V8 編譯 JS 代碼的過(guò)程

          1. 生成抽象語(yǔ)法樹(shù)(AST)和執(zhí)行上下文
          2. 第一階段是分詞(tokenize),又稱為詞法分析
          3. 第二階段是解析(parse),又稱為語(yǔ)法分析
          4. 生成字節(jié)碼
          5. 字節(jié)碼就是介于 AST 和機(jī)器碼之間的一種代碼。但是與特定類型的機(jī)器碼無(wú)關(guān),字節(jié)碼需要通過(guò)解釋器將其轉(zhuǎn)換為機(jī)器碼后才能執(zhí)行。
          6. 執(zhí)行代碼

          JS 執(zhí)行代碼的過(guò)程

          • 執(zhí)行全局代碼時(shí),創(chuàng)建全局上下文
          • 調(diào)用函數(shù)時(shí),創(chuàng)建函數(shù)上下文
          • 使用 eval 函數(shù)時(shí),創(chuàng)建 eval 上下文
          • 執(zhí)行局部代碼時(shí),創(chuàng)建局部上下文

          關(guān)于 Babel

          Babel ,又名 Babel.js。 是一個(gè)用于 web 開(kāi)發(fā),且自由開(kāi)源的 JavaScript 編譯器、轉(zhuǎn)譯器。

          Babel 的編譯流程:

          圖片來(lái)源:透過(guò)製作 Babel-plugin 初訪 AST

          Parse

          Babel 的第一步就是將源碼轉(zhuǎn)換為抽象語(yǔ)法樹(shù)(AST)

          const babel = require('@babel/core');
          const { parseAsync } = babel;
          const parseCode = async (code = '', options = {}) => {
            const res = await parseAsync(code, options);
          };
          parseCode(`
            const a = 1;
          `
          )

          可通過(guò) https://astexplorer.net/ 在線查看具體結(jié)果

          這一步會(huì)將收集到的的代碼,通過(guò) 詞法分析(Lexical analysis) 跟 語(yǔ)法分析(Parsing) 兩個(gè)階段將代碼轉(zhuǎn)換成 AST

          詞法分析(Lexical analysis)

          詞法分析會(huì)將代碼轉(zhuǎn)為 token ,可以理解為是對(duì)每個(gè)不可分割單詞元的描述,例如 const 就會(huì)轉(zhuǎn)換成下面這樣:

          Token {
              type: 
                  TokenType {
                  label: 'const',
                  keyword: 'const',
                  beforeExpr: false,
                  startsExpr: false,
                  rightAssociative: false,
                  isLoop: false,
                  isAssign: false,
                  prefix: false,
                  postfix: false,
                  binop: null,
                  updateContext: null
              },
              value: 'const',
              start: 5,
              end: 10,
              loc: 
              SourceLocation {
                  start: Position { line: 2, column: 4 },
                  end: Position { line: 2, column: 9 },
                  filename: undefined,
                  identifierName: undefined
              }
          }

          type 就是 對(duì) token 的描述,如果想要查看 bebal 生成的 token,我們可以在 options 里寫入:

          parserOpts: {
            tokenstrue
          }

          關(guān)于 @babel/parser  更多配置,可查看:https://babeljs.io/docs/en/babel-parser#options

          語(yǔ)法分析(Parsing)

          語(yǔ)法分析則是將上述的 token 轉(zhuǎn)換成對(duì)應(yīng)的 ast 結(jié)構(gòu)

          所以我們就可以看到這樣的一段樹(shù)狀結(jié)構(gòu)(過(guò)濾部分信息)

          {
              "type""VariableDeclaration",
              "start"0,
              "end"14,
              "loc": {
                  "start": {
                      "line"1,
                      "column"0
                  },
                  "end": {
                      "line"1,
                      "column"14
                  }
              },
              "declarations": [
                  {
                      "type""VariableDeclarator",
                      "start"6,
                      "end"13,
                      "loc": {
                          "start": {
                              "line"1,
                              "column"6
                          },
                          "end": {
                              "line"1,
                              "column"13
                          }
                      },
                      "id": {
                          "type""Identifier",
                          "start"6,
                          "end"9,
                          "loc": {
                              "start": {
                                  "line"1,
                                  "column"6
                              },
                              "end": {
                                  "line"1,
                                  "column"9
                              },
                              "identifierName""abc"
                          },
                          "name""abc"
                      },
                      "init": {
                          "type""NumericLiteral",
                          "start"12,
                          "end"13,
                          "loc": {
                              "start": {
                                  "line"1,
                                  "column"12
                              },
                              "end": {
                                  "line"1,
                                  "column"13
                              }
                          },
                          "extra": {
                              "rawValue"1,
                              "raw""1"
                          },
                          "value"1
                      }
                  }
              ],
              "kind""const"
          }

          這樣與 type 同級(jí)的結(jié)構(gòu)就叫  節(jié)點(diǎn)(Node) , locstartend 則是位置信息

          Transform

          Babel 的第二步就是遍歷 AST,并調(diào)用 transform 以訪問(wèn)者模式進(jìn)行修改

          export default function (babel{
            const { types: t } = babel;
            
            return {
              name"ast-transform"// not required
              visitor: {
                Identifier(path) {
                  path.node.name = path.node.name.split('').reverse().join('');
                }
              }
            };
          }

          通過(guò)執(zhí)行上述的 transform ,我們可以有:

          上述功能也可通過(guò) https://astexplorer.net/ 在線查看

          Generate

          Babel 的第三步就是把轉(zhuǎn)換后的 AST 打印成目標(biāo)代碼,并生成 sourcemap

          開(kāi)發(fā)一個(gè) babel 插件

          前置知識(shí) - 訪問(wèn)者模式

          訪問(wèn)者模式: 在訪問(wèn)者模式(Visitor Pattern)中,我們使用了一個(gè)訪問(wèn)者類,它改變了元素類的執(zhí)行算法。通過(guò)這種方式,元素的執(zhí)行算法可以隨著訪問(wèn)者改變而改變。這種類型的設(shè)計(jì)模式屬于行為型模式。根據(jù)模式,元素對(duì)象已接受訪問(wèn)者對(duì)象,這樣訪問(wèn)者對(duì)象就可以處理元素對(duì)象上的操作。

          知道你們不想看文字描述,所以直接上代碼!

          class 漢堡包 {
              accept(fatBoyVisitor) {
                  fatBoyVisitor.visit(this);
              }
          };

          class 薯?xiàng)l {
              accept(fatBoyVisitor) {
                  fatBoyVisitor.visit(this);
              }
          };

          class 炸雞 {
              accept(fatBoyVisitor) {
                  fatBoyVisitor.visit(this);
              }
          };

          class FatBoy {
              constructor(foods) {
                  this.foods = foods;
              }

              accept(fatBoyFoodVisitor) {
                  this.foods.forEach(food => {
                      food.accept(fatBoyFoodVisitor);
                  });
              }
          };

          class FatBoyFoodVisitor {
              visit(food) {
                  console.log(`肥宅吃了${food.constructor.name}`);
              }
          };

          const fatBoy = new FatBoy([new 漢堡包(), new 薯?xiàng)l(), new 炸雞()]);
          fatBoy.accept(new FatBoyFoodVisitor());

          最終輸出結(jié)果是:

          肥宅吃了漢堡包
          肥宅吃了薯?xiàng)l
          肥宅吃了炸雞

          babel-plugin-transform-object-assign 源碼

          import { declare } from "@babel/helper-plugin-utils";

          export default declare(api => {
            api.assertVersion(7);

            return {
              name"transform-object-assign",

              visitor: {
                CallExpressionfunction(path, file{
                  if (path.get("callee").matchesPattern("Object.assign")) {
                    path.node.callee = file.addHelper("extends");
                  }
                },
              },
            };
          });

          上面的就是 babel-plugin-transform-object-assign 的源碼。

          • declare:是一個(gè)用于簡(jiǎn)化創(chuàng)建 transformer 的工具函數(shù)
          • assertVersion:檢查當(dāng)前 babel 的大版本
          • name:當(dāng)前插件的名字
          • visitor:對(duì)外提供修改內(nèi)容的訪問(wèn)者
          • CallExpression:函數(shù)調(diào)用的 type,每一句代碼都會(huì)生成對(duì)應(yīng)的 type,例如最上面的函數(shù)名 abc 則對(duì)應(yīng)的是一個(gè) Identifier 類型,如果需要修改某一個(gè) type 的代碼,則在里面創(chuàng)建對(duì)應(yīng)的 type 訪問(wèn)者進(jìn)行修改即可。

          具體生成的代碼如下:

          // input
          const a = Object.assign({ a1 }, { b2 });

          // output
          "use strict";

          function _extends({ _extends = Object.assign || function (targetfor (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(thisarguments); }

          const a = _extends({
            a1
          }, {
            b2
          });

          Babel 插件實(shí)戰(zhàn) - 清除 console 源碼

          先上代碼:

          const babel = require('@babel/core');
          const get = require('lodash/get');
          const eq = require('lodash/eq');

          const { transformAsync } = babel;

          const removeConsole = rootPath => ({
              visitor: {
                  ExpressionStatementpath => {
                      const name = get(path, 'node.expression.callee.object.name');
                      const CONSOLE_PREFIX = 'console';
                      if (!eq(name, CONSOLE_PREFIX)) {
                          return;
                      };
                      path.remove();
                  },
              }
          });

          const transformCode = async (code = '') => {
              const res = await transformAsync(code, {
                  plugins: [
                      removeConsole,
                  ],
              });

              console.log(res.code);
          };

          transformCode(`
              const a = 10;
              console.group('嚶嚶嚶');
              console.log(a);
              console.groupEnd();
          `
          );

          輸出結(jié)果:

          const a = 10;

          上面的功能就是我們?cè)诼暶髡Z(yǔ)句類型 ExpressionStatement 中實(shí)現(xiàn)的。

          node.expression 對(duì)應(yīng)的是當(dāng)前類型里的子表達(dá)式,在這個(gè)場(chǎng)景里,它的 type === 'CallExpression'

          callee 對(duì)應(yīng)的就是一個(gè)調(diào)用函數(shù)類型,在這個(gè)場(chǎng)景里,它的 type === 'MemberExpression'

          object 對(duì)應(yīng)的就是當(dāng)前調(diào)用函數(shù)的前置對(duì)象,它的 type === 'Identifier'name 則是 console

          所以我們的實(shí)現(xiàn)就很簡(jiǎn)單了,只要 name === 'console' ,我們就可以通過(guò)內(nèi)部暴露的 remove 方法直接刪除當(dāng)前代碼。

          Babel 插件實(shí)戰(zhàn) - 新的語(yǔ)法

          總所周知,JS 不能這么寫

          # python
          arr = [123]
          print(arr[-1]) # 3
          print(arr[len(arr) - 1]) # 3

          但是我們可以用魔法打敗魔法

          作為一個(gè)兇起來(lái)連自己都可以編譯的語(yǔ)言,這有多難呢~

          具體實(shí)現(xiàn)如下:

          const babel = require('@babel/core');
          const get = require('lodash/get');
          const tailIndex = rootPath => ({
              visitor: {
                  MemberExpressionpath => {
                      const {
                          object: obj,
                          property: prop,
                      } = get(path, 'node', {});

                      const isNotMatch = codeNotMatch(obj, prop);
                      if (isNotMatch) {
                          return;
                      };

                      const {
                          index,
                          operator,
                          name,
                      } = createMatchedKeys(obj, prop);

                      if (!index || !name) {
                          return;
                      };

                      const res = genHeadIndex(index, name, operator);

                      path.replaceWithSourceString(res);
                  },
              },
          });

          MemberExpression 就是當(dāng)前要處理的語(yǔ)句類型。

          codeNotMatch 是我們自己實(shí)現(xiàn)的函數(shù),用于判斷 node.objectnode.property 是否合法,具體實(shí)現(xiàn)如下:

          const t = require('@babel/types');

          const codeNotMatch = (obj, prop) => {
              const objIsIdentifier = t.isIdentifier(obj);
              const propIsUnaryExpression = t.isUnaryExpression(prop);

              const objNotMatch = !obj || !objIsIdentifier;
              const propNotMatch = !prop || !propIsUnaryExpression;

              return objNotMatch || propNotMatch;
          };

          這里的 require('@babel/types') 是 babel 的一個(gè)工具包,這里面我們運(yùn)用了它的語(yǔ)句判斷能力。這種 isXXX 的大體實(shí)現(xiàn)如下:

          function isIdentifier(node, opts{
            if (!node) return false;
            const nodeType = node.type;
            if (nodeType === 具體類型) {
              if (typeof opts === "undefined") {
                return true;
              } else {
                return shallowEqual(node, opts);
              }
            }
            return false;
          }

          上面的 shallowEqual 實(shí)現(xiàn)如下:

          function shallowEqual(actual, expected{
            const keys = Object.keys(expected);

            for (const key of keys) {
              if (actual[key] !== expected[key]) {
                return false;
              }
            }

            return true;
          }

          createMatchedKeys 用于創(chuàng)建最終匹配的字符,即需要將 -1 改為 .length - 1 的形式,所以具體實(shí)現(xiàn)如下:

          const createMatchedKeys = (obj, prop) => {
              const {
                  prefix,
                  operator,
                  argument: arg
              } = prop;

              let index;
              let name;

              const propIsArrayExpression = !!prefix && !!operator && !!arg;
              const argIsNumericLiteral = t.isNumericLiteral(arg);

              if (propIsArrayExpression && argIsNumericLiteral) {
                  index = get(arg, 'value');
                  name = get(obj, 'name');
              };

              return {
                  index,
                  operator,
                  name,
              };
          };

          這里面一路判斷,匹配即可。

          所以當(dāng)我們拿到下標(biāo) ,操作符 跟 數(shù)組名 之后,直接組合成最終要生成的代碼即可,即有:

          const genHeadIndex = (index, name, operator) => `${name}[${name}.length ${operator} ${index}]`;

          最后我們直接替換源碼即可,怎么替換呢,babel 有通過(guò)訪問(wèn)者模式返回 replaceWithSourceString 方法進(jìn)行硬編碼替換。。。

          替換的邏輯就是先通過(guò)  babel.parse  將要替換的代碼生成 ast,然后從 loc 到具體的 node 進(jìn)行替換。

          一個(gè)新語(yǔ)法,就這么完成啦~

          參考資料

          1. 透過(guò)製作 Babel-plugin 初訪 AST
          2. 詞法分析(Lexical analysis)
          3. 語(yǔ)法分析(Parsing)
          4. https://babeljs.io/docs/en/babel-parser#options
          5. https://astexplorer.net/
          6. https://github.com/babel/babel
          7. https://github.com/babel/minify
          8. 『1W7字中高級(jí)前端面試必知必會(huì)』終極版
          9. Babel 插件手冊(cè)


          瀏覽 30
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  国产一级电影在线看 | 国产91女技师一区二区三区 | 蜜臀久久精品久久久久久酒店 | 伊人成人大香蕉网 | 吸咬奶头狂揉60分钟视频 |