自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

深入理解 Babel - 微內(nèi)核架構(gòu)與 ECMAScript 標(biāo)準(zhǔn)化

開(kāi)發(fā) 前端
本文介紹了 Babel 的概述/微內(nèi)核架構(gòu)/ECMAScript標(biāo)準(zhǔn)化方面的設(shè)計(jì)思想和部分實(shí)現(xiàn)原理。

隨著瀏覽器版本的持續(xù)更新,瀏覽器對(duì)JavaScript的支持越來(lái)越強(qiáng)大,Babel的重要性顯得較低了。但Babel的設(shè)計(jì)思路、背后依賴(lài)的ECMAScript標(biāo)準(zhǔn)化思想仍然值得借鑒。

本文涉及的Babel版本主要是V7.16及以下,截至發(fā)文時(shí),Babel最新發(fā)布的版本是V7.25.6,未出現(xiàn)大版本更新,近2年也進(jìn)入了穩(wěn)定迭代期,本文的分析思路基本適用目前的Babel設(shè)計(jì)。

一、Babel簡(jiǎn)介

Babel是什么

Babel是JavaScript轉(zhuǎn)譯器,通過(guò)Babel,開(kāi)發(fā)者可以自由使用下一代ECMAScript 語(yǔ)法。高版本ECMAScript語(yǔ)法將被轉(zhuǎn)譯為低版本語(yǔ)法,以便順利運(yùn)行在各類(lèi)環(huán)境,如低版本瀏覽器、低版本 Node.js 等。

Babel 是轉(zhuǎn)譯器,不是編譯器。下面是轉(zhuǎn)譯和編譯的區(qū)別:

編譯,一般指將一種語(yǔ)言轉(zhuǎn)換為另一種語(yǔ)法和抽象程度等都不同的語(yǔ)言,常見(jiàn)的比如 gcc 編譯器。

轉(zhuǎn)譯,一般指將一種語(yǔ)言轉(zhuǎn)換為不同版本或者抽象程度相同的語(yǔ)言,比如 Babel 可以把 ECMAScript 6 語(yǔ)法轉(zhuǎn)譯為 ECMAScript 5語(yǔ)法。

利用 Babel,開(kāi)發(fā)者可以使用 ECMAScript 的各種新特性進(jìn)行開(kāi)發(fā),同時(shí)花極少的精力關(guān)注瀏覽器或其他JS運(yùn)行環(huán)境對(duì)新特性的支持。甚至,開(kāi)發(fā)者可以根據(jù)自身需要,創(chuàng)造屬于自己的 JavaScript 語(yǔ)法。

Babel在轉(zhuǎn)譯的時(shí)候,會(huì)對(duì)源碼進(jìn)行以下處理: 語(yǔ)法轉(zhuǎn)譯(Syntax)和添加API Polyfill。

圖片圖片

  • 語(yǔ)法(Syntax)部分Babel 支持識(shí)別高版本語(yǔ)法,并通過(guò)插件將源碼從高版本語(yǔ)法轉(zhuǎn)譯為低版本語(yǔ)法,如:

箭頭函數(shù) () => {} 轉(zhuǎn)為普通函數(shù) function() {}。

const / let 轉(zhuǎn)譯為var

  • API Polyfill有些運(yùn)行時(shí)相關(guān)的 API,語(yǔ)法轉(zhuǎn)譯無(wú)法解決它們對(duì)低版本瀏覽器等環(huán)境的兼容性問(wèn)題,因此 Babel 通過(guò)與 core-js 等工具的配合,實(shí)現(xiàn) API 部分對(duì)目標(biāo)環(huán)境(通常是低版本瀏覽器等)的兼容。例如[1, 2, 3].include、Promise等 API,Babel 在處理時(shí),如果目標(biāo)環(huán)節(jié)可能不支持原生的 include / Promise 的話(huà),Babel 會(huì)在轉(zhuǎn)譯結(jié)果中嵌入 include / Promise 的自定義實(shí)現(xiàn)。有多種方式可以使用 Babel,如: 命令行(babel-cli、babel-node)、瀏覽器(babel-standalone)、API 調(diào)用(babel-core)、webpack loader(babel-loader)等。

轉(zhuǎn)譯過(guò)程

和多數(shù)轉(zhuǎn)譯器相同,Babel 運(yùn)行的生命周期主要是 3 個(gè)階段: 解析、轉(zhuǎn)換、代碼生成。

這個(gè)過(guò)程涉及抽象語(yǔ)法樹(shù):

抽象語(yǔ)法樹(shù)(Abstract Syntax Tree,AST),或簡(jiǎn)稱(chēng)語(yǔ)法樹(shù)(Syntax tree),是源代碼語(yǔ)法結(jié)構(gòu)的一種抽象表示。

AST 是樹(shù)形對(duì)象,以結(jié)構(gòu)化的形式表示編程語(yǔ)言的語(yǔ)法結(jié)構(gòu),樹(shù)上的每個(gè)節(jié)點(diǎn)都表示源代碼中的一種結(jié)構(gòu)。

圖片圖片

源碼字符串需要經(jīng)轉(zhuǎn)譯器生成 AST,轉(zhuǎn)譯器有很多種,不同轉(zhuǎn)譯器,生成的AST對(duì)象格式細(xì)節(jié)可能有差異,但共同點(diǎn)為: 都是樹(shù)形對(duì)象、該樹(shù)形對(duì)象描述了節(jié)點(diǎn)特征、各節(jié)點(diǎn)之間的關(guān)系(兄弟、父子等)。

以下是 Babel 生命周期的三個(gè)過(guò)程:

  • 解析(Parsing): Code1 ==> 抽象語(yǔ)法樹(shù)1解析過(guò)程包括 2 個(gè)環(huán)節(jié): 詞法解析、語(yǔ)法解析,最終生成抽象語(yǔ)法樹(shù)。詞法解析階段,代碼字符串被解析為 token 令牌數(shù)組,數(shù)組項(xiàng)是一個(gè)對(duì)象,包括: 代碼字符碎片的值、位置、類(lèi)型等信息。token 數(shù)組是平鋪式的數(shù)組,沒(méi)有嵌套的結(jié)構(gòu)信息,它是為語(yǔ)法解析階段做準(zhǔn)備的。語(yǔ)法解析階段,token 令牌數(shù)組被解析為結(jié)構(gòu)化的抽放語(yǔ)法樹(shù)對(duì)象(AST)。babel-parser 完成該階段的主要功能。

圖片圖片

  • 轉(zhuǎn)換(Transformation): AST1 ==> AST2Babel 生成 AST 后,會(huì)對(duì) AST 進(jìn)行遍歷,遍歷過(guò)程中,各類(lèi)插件對(duì)原 AST 對(duì)象進(jìn)行增刪改查等操作,AST 結(jié)構(gòu)被修改。

圖片圖片

  • 代碼生成(Generation): AST2 ==> Code2Babel 將修改后的 AST 對(duì)象轉(zhuǎn)目標(biāo)代碼字符串。babel-generator 完成該階段的主要功能。

圖片圖片

二、Babel微內(nèi)核架構(gòu)

微內(nèi)核架構(gòu)

Babel 采用微內(nèi)核架構(gòu),其內(nèi)核保留核心功能,其余功能利用外部工具和插件機(jī)制實(shí)現(xiàn),也體現(xiàn)了"開(kāi)放-封閉"的設(shè)計(jì)原則。

圖片圖片

除了微內(nèi)核設(shè)計(jì)架構(gòu),Babel 的模塊設(shè)計(jì)也可以做如下分類(lèi):

圖片圖片

轉(zhuǎn)譯模塊

轉(zhuǎn)譯模塊位于 Babel 微內(nèi)核架構(gòu)的"微內(nèi)核"部分,該部分主要負(fù)責(zé)代碼轉(zhuǎn)譯,也就是上面提到的"解析-轉(zhuǎn)換-代碼生成"過(guò)程。

該模塊主要包括: babel-parser、babel-traverse、babel-generator。

  • babel-parser負(fù)責(zé)將代碼字符串轉(zhuǎn)為 AST 對(duì)象。有 2 點(diǎn)值得一提:

babel-parser 本身并不會(huì)對(duì) AST 做轉(zhuǎn)換操作,只是負(fù)責(zé)解析出 AST。AST 轉(zhuǎn)換部分交由各類(lèi) plugins 和 presets 處理。

babel-parser 內(nèi)置了對(duì) ESNext/TypeScript/JSX/Flow 最新版本語(yǔ)法的支持,但很多默認(rèn)是不開(kāi)啟的,目前沒(méi)有開(kāi)放插件機(jī)制擴(kuò)展新語(yǔ)法。

  • babel-traverse在轉(zhuǎn)譯過(guò)程中,babel-traverse 負(fù)責(zé)遍歷 AST 節(jié)點(diǎn),并根據(jù)配置的 plugins/presets,在遍歷過(guò)程中,對(duì)各個(gè) AST 節(jié)點(diǎn)進(jìn)行增刪改查的操作。AST 是一個(gè)樹(shù)形對(duì)象,遍歷 AST 對(duì)象的過(guò)程也是一個(gè)深度優(yōu)先遍歷的過(guò)程。

  • babel-generator負(fù)責(zé)將 AST 節(jié)點(diǎn),轉(zhuǎn)為代碼字符串,同時(shí)也可以生成 source-map。

插件模塊

插件模塊包括 plugins、presets。

  • plugins豐富的插件,幫助 Babel 成為一個(gè)非常成功的轉(zhuǎn)譯工具。對(duì) AST 的遍歷、轉(zhuǎn)換是 Babel 轉(zhuǎn)譯的核心功能,但 Babel 本身并不參與該過(guò)程,將這些功能作為插件引入到運(yùn)行時(shí)。具體來(lái)說(shuō),babel-core 作為核心工具,不提供對(duì) AST 的修改邏輯,通過(guò)調(diào)用各類(lèi)插件,實(shí)現(xiàn)對(duì) AST 的修改。Babel的插件分為語(yǔ)法插件和轉(zhuǎn)換插件。

語(yǔ)法插件

值得注意的是,babel-parser 負(fù)責(zé)將 JavaScript 代碼解析出抽象語(yǔ)法樹(shù)(AST),它支持全面識(shí)別 ESNext/TypeScript/JSX/Flow 等語(yǔ)法,目前由 Babel 團(tuán)隊(duì)開(kāi)發(fā)維護(hù),不支持插件化。

Babel 插件生態(tài)中的語(yǔ)法插件,其功能就是作為"開(kāi)關(guān)",配置是否開(kāi)啟 babel-parser 的某些語(yǔ)法轉(zhuǎn)譯功能。

語(yǔ)法插件在 Babel 源碼中,以 babel-plugin-syntax 開(kāi)頭。

舉個(gè)例子:

babel-plugin-syntax-decorators負(fù)責(zé)開(kāi)啟 babel-parser 對(duì)裝飾器的語(yǔ)法支持。

babel-plugin-syntax-dynamic-import負(fù)責(zé)開(kāi)啟 babel-parser 對(duì) import 語(yǔ)句的語(yǔ)法支持。

babel-plugin-syntax-jsx負(fù)責(zé)開(kāi)啟 babel-parser 對(duì) jsx 語(yǔ)法的支持。

  • 轉(zhuǎn)換插件轉(zhuǎn)換插件就是社區(qū)里常說(shuō)的 Babel 插件,負(fù)責(zé)轉(zhuǎn)換 AST 節(jié)點(diǎn)。在介紹babel-traverse時(shí)提到,它負(fù)責(zé)遍歷AST對(duì)象,每個(gè)AST節(jié)點(diǎn)會(huì)被訪問(wèn)到,等待轉(zhuǎn)換,轉(zhuǎn)換的部分,由"轉(zhuǎn)換插件"負(fù)責(zé)。轉(zhuǎn)換插件會(huì)提供一個(gè)叫做"Visitor"的對(duì)象,該對(duì)象的 Key 為節(jié)點(diǎn)名稱(chēng),Value 部分提供進(jìn)入該節(jié)點(diǎn)時(shí)、離開(kāi)該節(jié)點(diǎn)時(shí)的回調(diào)函數(shù),在回調(diào)函數(shù)里,可以對(duì)該節(jié)點(diǎn)進(jìn)行一系列操作。"Visitor" 又稱(chēng)為 "訪問(wèn)者"。
// plugin 提供 visitor,在 visitor 中對(duì) AST 節(jié)點(diǎn)操作
const visitor = {
    Program: {
        enter() {},
        exit() {},
    },


    CallExpression: {
        enter() {},
        exit() {},
    },


    NumberLiteral: {
        enter() {},
        exit() {},
    }
};
traverse(ast, visitor);

轉(zhuǎn)換插件在Babel源碼中,以 babel-plugin-transform 開(kāi)頭。

舉個(gè)例子:

babel-plugin-transform-strict-mode

該插件攔截 Program 節(jié)點(diǎn),也就是整個(gè)程序的根節(jié)點(diǎn),添加 "use strict"指令。

visitor 節(jié)點(diǎn)值為函數(shù)時(shí),是 enter 回調(diào)的快捷方式。

{
    name: "transform-strict-mode",


    visitor: {
      Program(path) {
        const { node } = path;


        for (const directive of node.directives) {
          if (directive.value.value === "use strict") return;
        }


        path.unshiftContainer(
          "directives",
          t.directive(t.directiveLiteral("use strict")),
        );
      },
    },
  };
}
  • babel-plugin-transform-object-assign

該插件負(fù)責(zé)攔截函數(shù)調(diào)用表達(dá)式節(jié)點(diǎn) CallExpression,將 Object.assign 轉(zhuǎn)為 extends 寫(xiě)法。

{
    name: "transform-object-assign",


    visitor: {
      CallExpression(path, file) {
        if (path.get("callee").matchesPattern("Object.assign")) {
          path.node.callee = file.addHelper("extends");
        }
      },
    },
}
  • PresetsBabel 插件的功能是細(xì)粒度的,大部分插件承擔(dān)了單一功能。而在實(shí)際開(kāi)發(fā)過(guò)程中,往往需要支持對(duì)各類(lèi)語(yǔ)法的支持。此時(shí),有兩種做法:

需要支持哪些特性,就分別引入支持該特性的插件

直接引入一個(gè)插件集合,涵蓋所需的各類(lèi)插件功能

很顯然,第一種做法是相對(duì)麻煩的。針對(duì)第二種做法,Babel提供了插件集 preset。

preset 在 Babel 源碼中,以 babel-preset 開(kāi)頭。

例如,Babel 已經(jīng)提供了幾種常用的 preset 供開(kāi)發(fā)者使用:

babel-preset-env

babel-preset-react

babel-preset-flow

babel-preset-typescript

  • 插件運(yùn)行順序Babel 配置項(xiàng)中,plugins 和 presets 均以數(shù)組的形式配置,執(zhí)行時(shí)有先后順序。

plugins 在 presets之前運(yùn)行

plugins 按照數(shù)組正序執(zhí)行

presets 按照數(shù)組倒序執(zhí)行

圖片圖片

工具模塊

工具模塊提供 Babel 相關(guān)模塊所需的各類(lèi)工具,以下一一簡(jiǎn)要介紹:

  • babel-corebabel-core 對(duì)外提供了 Babel 多項(xiàng)功能的 API,如轉(zhuǎn)譯文件、轉(zhuǎn)譯代碼、創(chuàng)建/獲取配置等,在 Babel 官方文檔介紹的比較詳細(xì),不再贅述。值得注意的是,轉(zhuǎn)譯類(lèi)的 API 均提供了同步和異步版本,如 transformSync/transfomAsync、parseSync/parseAsync。

  • babel-cliBabel 的命令行工具,可以直接轉(zhuǎn)譯文件夾/文件,它也提供了很多配置項(xiàng)做其他工作,官方文檔介紹的比較詳細(xì),感興趣的同學(xué)可以去 Babel 官網(wǎng)查看詳細(xì)配置。

  • babel-standaloneBabel 對(duì)外服務(wù)的很多包是基于 node 環(huán)境下使用的,babel-standalone 提供了瀏覽器下轉(zhuǎn)譯的方案。babel-standalone 內(nèi)置了所有 Babel 插件,所以體積還是比較大的,而且在瀏覽器端轉(zhuǎn)譯需要時(shí)間,比較適合開(kāi)發(fā)、學(xué)習(xí)使用,不適合在生產(chǎn)環(huán)境使用。舉個(gè)例子:
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>test babel-standalone</title>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <script type="text/babel">
      const arr = [1, 2, 3];
      console.log(...arr);
</script>
  </head>
  <body></body>
</html>

在瀏覽器運(yùn)行該 html,可以看到,頁(yè)面結(jié)構(gòu)變成了:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>test babel-standalone</title>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <script type="text/babel">
      const arr = [1, 2, 3];
      console.log(...arr);
</script>
    <script>
      "use strict";
      var _console;
      var arr = [1, 2, 3];
      (_console = console).log.apply(_console, arr); //# sourceMappingURL=data:application/json;charset=utf-8;base64...
</script>
  </head>
  <body></body>
</html>
  • babel-node

提供在命令行執(zhí)行高級(jí)語(yǔ)法的環(huán)境。

例如:

// index.js 里可以使用高級(jí)語(yǔ)法     
babel-node -e index.js

index.js 文件以及被其引入的其他文件均可以使用高級(jí)語(yǔ)法了。和 babel-cli 不同的是,babel-cli 只負(fù)責(zé)轉(zhuǎn)換,不在 node 運(yùn)行時(shí)執(zhí)行;babel-node 會(huì)在 node 運(yùn)行時(shí)執(zhí)行轉(zhuǎn)換,不適合生產(chǎn)環(huán)境使用。

  • babel-register

在源文件中,引入babel-register,如 index.js:

index.js

require('babel-register');     
require('./run');

run.js

import fs from 'fs';     
console.log(fs);

執(zhí)行 node index 時(shí),run.js 就不需要被轉(zhuǎn)碼了。

babel-register 通過(guò)攔截 node require 方法,為 node 運(yùn)行時(shí)引入了 Babel 的轉(zhuǎn)譯能力。

  • babel-loader

babel-loader 是利用 babel-core 的 API 封裝的 webpack loader,用于 webpack 構(gòu)建過(guò)程。

  • babel-types

babel-types 是一個(gè)非常強(qiáng)大的工具集合,它集成了節(jié)點(diǎn)校驗(yàn)、增刪改查等功能,是 Babel 核心模塊開(kāi)發(fā)、插件開(kāi)發(fā)等場(chǎng)景下不可或缺的工具。

例如:

const t = require('@babel/types');
const binaryExpression = t.binaryExpression('+', t.numericLiteral(1), t.numericLiteral(2));
  • babel-template

模板引擎,負(fù)責(zé)將代碼字符串轉(zhuǎn)為 AST 節(jié)點(diǎn)對(duì)象。

import { smart as template } from '@babel/template';
    import generate from '@babel/generator';
    import * as t from '@babel/types';


    const buildRequire = template(      var %%importName%% = require(%%source%%);    );


    const ast = buildRequire({
        importName: t.identifier('myModule'),
        source: t.stringLiteral("my-module"),
    });


    const code = generate(ast).code


    console.log(code)

        運(yùn)行結(jié)果:

var myModule = require("my-module");
  • babel-code-frame

負(fù)責(zé)打印出錯(cuò)的代碼位置,例如:

const { codeFrameColumns } = require('@babel/code-frame');


const testCode = `
class Run {
    constructor() {}
}
`;


const location = {
    start: {
        line: 2,
        column: 2,
    }
};


const result = codeFrameColumns(testCode, location);


console.log(result);
1 | class Run {
> 2 |     constructor() {}
    |  ^
  3 | }
  4 |
  • babel-highlight

向控制臺(tái)輸出有顏色的代碼片段。該工具可以識(shí)別 JavaScript 中的操作符號(hào)、標(biāo)識(shí)符、保留字等類(lèi)型的詞法單元,并在終端環(huán)境下顯示不同的顏色。

運(yùn)行時(shí)相關(guān)模塊

Babel 配合其插件可以對(duì)靜態(tài)代碼進(jìn)行轉(zhuǎn)譯,但有一些遺漏點(diǎn):

  • 對(duì)于運(yùn)行時(shí)涉及的一些高版本 API,并沒(méi)有提供兼容目標(biāo)環(huán)境的 Polyfill。
  • 轉(zhuǎn)譯產(chǎn)物代碼可能有些臃腫。

為此,運(yùn)行時(shí)模塊(runtime)關(guān)注的是轉(zhuǎn)譯產(chǎn)物的運(yùn)行時(shí)環(huán)境,對(duì)運(yùn)行時(shí)提供 API polyfill、代碼優(yōu)化等,該模塊涉及幾個(gè)子包:

  • babel-preset-env
  • babel-plugin-transform-runtime
  • babel-runtime
  • babel-runtime-corejs2/3
  • core-js

接下來(lái)以案例解釋 runtime 模塊的作用。

源碼文件 index.js 的內(nèi)容:

const a = 1; // const 為語(yǔ)法部分
class Base {} // class 為語(yǔ)法部分
new Promise() // Promise 為 API 部分

這段源碼包含了語(yǔ)法和 API 部分:

  • const、class 為語(yǔ)法部分
  • Promise 為 API 部分

如果希望這段源碼轉(zhuǎn)為 ES5 版本,使構(gòu)建產(chǎn)物可以運(yùn)行在不支持 ES6 和 Promise 的環(huán)境里,該怎么做呢?

用 babel 命令行執(zhí)行轉(zhuǎn)譯,其中源文件為 index.js,轉(zhuǎn)譯產(chǎn)物文件為 index-compiled.js。

npx babel index.js --out-file index-compiled.js

需要配置.babelrc 幫助 Babel 完成語(yǔ)法和 API 部分的轉(zhuǎn)譯:

.babelrc:

{
    "presets": [
        [ 
            "@babel/preset-env"
        ]
    ],
    "plugins": [
        [
            "@babel/plugin-transform-runtime",
            {
                "corejs": 3
            }
        ]
    ]
}

簡(jiǎn)要解釋下該配置的原理:

  • babel-preset-env 可以完成語(yǔ)法部分轉(zhuǎn)譯,即 const 轉(zhuǎn)譯為var但構(gòu)建產(chǎn)物中,有些輔助代碼如 _classCallCheck 是以硬編碼的形式直接寫(xiě)入轉(zhuǎn)譯產(chǎn)物的:
"use strict";


    function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }


    var a = 1;


    var Base = function Base() {
        _classCallCheck(this, Base);
    };


    new Promise();

這樣的后果就是構(gòu)建產(chǎn)物比較臃腫。

  • babel-plugin-transform-runtime 可以將上述 _classCallCheck 置于通用包中,以引用的形式寫(xiě)入轉(zhuǎn)譯產(chǎn)物:
"use strict";


    var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");


    var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));


    var a = 1;


    var Base = function Base() {
        (0, _classCallCheck2["default"])(this, Base);
    };


    new Promise();
  • babel-plugin-transform-runtime 的配置參數(shù) corejs 用于轉(zhuǎn)譯 API 部分,如 Promsie
"use strict";


    var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");


    var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));


    var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck"));


    var a = 1;


    var Base = function Base() {
        (0, _classCallCheck2["default"])(this, Base);
    };


    new _promise"default";

Babel 轉(zhuǎn)譯過(guò)程的運(yùn)行時(shí)優(yōu)化是一個(gè)繁瑣的過(guò)程,為此將單獨(dú)用一章講解運(yùn)行時(shí)優(yōu)化,感興趣的同學(xué)可以直接閱讀 "Babel Runtime" 章節(jié)詳細(xì)了解。

三、標(biāo)準(zhǔn)化

Babel 生態(tài)涉及的一些標(biāo)準(zhǔn)化組織。無(wú)論是 JavaScript、HTML、DOM、URL 等領(lǐng)域,均需要統(tǒng)一的標(biāo)準(zhǔn),才能在不同的運(yùn)行環(huán)境下有統(tǒng)一的表現(xiàn)。Babel 轉(zhuǎn)譯也需要遵循這些標(biāo)準(zhǔn),包括 ECMAScript、web標(biāo)準(zhǔn)等。

ECMAScript

JavaScript誕生

1995 年,JavaScript 的第一個(gè)版本發(fā)布。用時(shí)間線(xiàn)的方式描述 JavaScript 的誕生過(guò)程會(huì)更清晰: 

圖片圖片

ECMAScript發(fā)布

1996 年,微軟模仿 JavaScript 實(shí)現(xiàn)了 JScript 并內(nèi)置在 IE3.0,隨后,Netscape 公司著手推動(dòng) JavaScript 標(biāo)準(zhǔn)化。

這里涉及幾個(gè)組織:

  • Ecma International

Ecma International 是一家國(guó)際性會(huì)員制度的信息和電信標(biāo)準(zhǔn)組織。1994年之前,名為歐洲計(jì)算機(jī)制造商協(xié)會(huì)(European Computer Manufacturers Association)。因?yàn)橛?jì)算機(jī)的國(guó)際化,組織的標(biāo)準(zhǔn)牽涉到很多其他國(guó)家,因此組織決定改名表明其國(guó)際性。

Ecma International 的任務(wù)包括與有關(guān)組織合作開(kāi)發(fā)通信技術(shù)和消費(fèi)電子標(biāo)準(zhǔn)、鼓勵(lì)準(zhǔn)確的標(biāo)準(zhǔn)落實(shí)、和標(biāo)準(zhǔn)文件與相關(guān)技術(shù)報(bào)告的出版。

Ecma International 負(fù)責(zé)多個(gè)國(guó)際標(biāo)準(zhǔn)的制定:

  • CD-ROM 格式(之后被國(guó)際標(biāo)準(zhǔn)化組織批準(zhǔn)為ISO 9660)
  • C# 語(yǔ)言規(guī)范
  • C++/CLI 語(yǔ)言規(guī)范
  • 通用語(yǔ)言架構(gòu)(CLI)
  • Eiffel 語(yǔ)言
  • 電子產(chǎn)品環(huán)境化設(shè)計(jì)要素
  • Universal 3D 標(biāo)準(zhǔn)
  • OOXML
  • Dart 語(yǔ)言規(guī)范
  • ECMAScript 語(yǔ)言規(guī)范(以 JavaScript 為基礎(chǔ))ECMA-262其中就包括 JavaScript 標(biāo)準(zhǔn)語(yǔ)言規(guī)范 ECMAScript。cma International 擁有 ECMAScript 的商標(biāo)。
  • ECMA TC39

「TC39」全稱(chēng)「Technical Committee 39」譯為「第 39 號(hào)技術(shù)委員會(huì)」,是 Ecma International 組織架構(gòu)中的一部分。

TC39 負(fù)責(zé)迭代和發(fā)展 ECMAScript,它的成員由各個(gè)主流瀏覽器廠商的代表組成,通常每年召開(kāi)約 6 次會(huì)議來(lái)討論未決提案的進(jìn)展情況,會(huì)議的每一項(xiàng)決議必須得到大部分人的贊同,并且沒(méi)有人強(qiáng)烈反對(duì)才可以通過(guò)。

TC39 負(fù)責(zé):

維護(hù)和更新 ECMAScript 語(yǔ)言標(biāo)準(zhǔn)

識(shí)別、開(kāi)發(fā)、維護(hù) ECMAScript 的擴(kuò)展功能庫(kù)

開(kāi)發(fā)測(cè)試套件

為 ISO/IEC JTC 1 提供標(biāo)準(zhǔn)

評(píng)估和考慮新添加的標(biāo)準(zhǔn)

  • ISO

國(guó)際標(biāo)準(zhǔn)化組織(英語(yǔ): International Organization for Standardization,簡(jiǎn)稱(chēng): ISO)成立于 1947 年 2 月 23 日,制定全世界工商業(yè)國(guó)際標(biāo)準(zhǔn)的國(guó)際標(biāo)準(zhǔn)建立機(jī)構(gòu)。

ISO 的國(guó)際標(biāo)準(zhǔn)以數(shù)字表示,例如: "ISO 11180:1993" 的 "11180" 是標(biāo)準(zhǔn)號(hào)碼,而 "1993" 是出版年份。

ISO/IEC JTC 1 是國(guó)際標(biāo)準(zhǔn)化組織和國(guó)際電工委員會(huì)聯(lián)合技術(shù)委員會(huì)。其目的是開(kāi)發(fā)、維護(hù)和促進(jìn)信息技術(shù)以及信息和通信技術(shù)領(lǐng)域的標(biāo)準(zhǔn)。JTC 1 負(fù)責(zé)了許多關(guān)鍵的 IT 標(biāo)準(zhǔn),從 MPEG 視頻格式到 C++ 編程語(yǔ)言。

圖片圖片

  • ECMAScript 發(fā)展過(guò)程中的關(guān)鍵節(jié)點(diǎn)

圖片圖片

ECMAScript 各版本

ECMAScript 經(jīng)歷了多個(gè)版本,每個(gè)版本有自己的特點(diǎn),簡(jiǎn)單列舉如下: 

圖片圖片

圖片圖片

ECMAScript 迭代過(guò)程

一個(gè) ECMAScript 標(biāo)準(zhǔn)的制作過(guò)程,包含了 Stage 0 到 Stage 4 共 5 個(gè)階段,每個(gè)階段提交至下一階段都需要 TC39 審批通過(guò)。

圖片圖片

圖片圖片

特性進(jìn)入 Stage-4 后,才有可能被加入標(biāo)準(zhǔn)中,還需要 ECMA General Assembly 表決通過(guò)才能進(jìn)入下一次的 ECMAScript 標(biāo)準(zhǔn)中。

如何閱讀 ECMAScript

ECMAScript 文檔結(jié)構(gòu)

ECMAScript 的規(guī)格,可以在 ECMA 國(guó)際標(biāo)準(zhǔn)組織的官方網(wǎng)站免費(fèi)下載和在線(xiàn)閱讀。

查看ECMAScript 不同版本的地址:https://ecma-international.org/publications-and-standards/standards/ecma-262/。

截至 2023年底,已發(fā)布的版本如下:

  • ECMA-262 5.1 edition, June 2011

(https://262.ecma-international.org/5.1/index.html)

  • ECMA-262, 6th edition, June 2015

(https://262.ecma-international.org/6.0/index.html)

  • ECMA-262, 7th edition, June 2016

(https://262.ecma-international.org/7.0/index.html)

  • ECMA-262, 8th edition, June 2017

(https://262.ecma-international.org/8.0/index.html)

  • ECMA-262, 9th edition, June 2018

(https://262.ecma-international.org/9.0/index.html)

  • ECMA-262, 10th edition, June 2019

(https://262.ecma-international.org/10.0/index.html)

  • ECMA-262, 11th edition, June 2020

(https://262.ecma-international.org/11.0/index.html)

  • ECMA-262, 12th edition, June 2021

(https://262.ecma-international.org/12.0/index.html)

  • ECMA-262, 13th edition, June 2022

(https://262.ecma-international.org/13.0/index.html)

  • ECMA-262, 14th edition, June 2023

(https://262.ecma-international.org/14.0/index.html)

每個(gè)版本有獨(dú)立的網(wǎng)址,格式為: https://262.ecma-international.org/{version}/,比如 ECMAScript 14.0 版本的網(wǎng)址為 https://262.ecma-international.org/14.0/。

從章節(jié)數(shù)量上,ECMAScript 6.0、ECMAScript 7.0 有 26 章,之后的版本有 27-29 章,雖然章節(jié)數(shù)量不同,規(guī)格章節(jié)的分布是保持一定規(guī)律的,以 ECMAScript 11.0 版本為例:

  • Introduction: 介紹部分

該章節(jié)簡(jiǎn)要描述了: JavaScript 和 ECMAScript 的發(fā)展歷史、不同 ECMAScript 規(guī)格的主要更新內(nèi)容。

  • 第 1 章到第 3 章: 描述了規(guī)格文件本身,而非語(yǔ)言

第 1 章用一句話(huà)描述了該規(guī)格的描述范圍。

第 2 章描述了基于規(guī)格的"實(shí)現(xiàn)"的一致性要求:

"實(shí)現(xiàn)"必須支持規(guī)格中描述的所有類(lèi)型、值、對(duì)象、屬性、函數(shù)以及程序的語(yǔ)法和語(yǔ)義

"實(shí)現(xiàn)"必須按照 Unicode 標(biāo)準(zhǔn)和 ISO/IEC 10646 的最新版本處理文本輸入

"實(shí)現(xiàn)"如果提供了應(yīng)用程序編程接口(API),那么該 API 需要適應(yīng)不同的人文語(yǔ)言和國(guó)家差異,且必須實(shí)現(xiàn)最新版本的 ECMA-402 所定義的與本規(guī)范相兼容的接口

"實(shí)現(xiàn)"可以支持該規(guī)格中沒(méi)有提及的類(lèi)型、值、對(duì)象、屬性、函數(shù)、正則表達(dá)式語(yǔ)法以及其他編程寫(xiě)法

"實(shí)現(xiàn)"不能實(shí)現(xiàn)該規(guī)格中禁止的寫(xiě)法

  • 第 3 章描述了該規(guī)格的一些參考資料:
  • ISO/IEC 10646
  • ECMA-402
  • EMCA-404 JSON 數(shù)據(jù)交換格式規(guī)范
  • 第 4 章: 對(duì)這門(mén)語(yǔ)言總體設(shè)計(jì)的描述。
  • 第 5 章到第 8 章: 語(yǔ)言宏觀層面的描述。
  • 第 6 章介紹數(shù)據(jù)類(lèi)型。
  • 第 7 章介紹語(yǔ)言?xún)?nèi)部用到的抽象操作。
  • 第 8 章介紹代碼如何運(yùn)行。
  • 第 9 章到第 27 章: 介紹具體的語(yǔ)法。

一般而言,除非寫(xiě)編譯器,開(kāi)發(fā)者無(wú)需閱讀 ECMAScript 的規(guī)格,規(guī)格的內(nèi)容非常多,如無(wú)必要也無(wú)需通讀。只是在遇到一些奇怪的問(wèn)題時(shí),閱讀官方規(guī)格,是最穩(wěn)妥的辦法。

通過(guò)閱讀規(guī)格解決一些問(wèn)題

(以ECMAScript 11.0為例)

  • 識(shí)別關(guān)鍵詞和保留字,并高亮

Babel 工具集中的 babel-highlight,可以實(shí)現(xiàn)在終端對(duì)代碼塊中的目標(biāo)字符單元顯示不同的顏色。這里需要識(shí)別不同字符單元的類(lèi)型,如關(guān)鍵字、保留字、標(biāo)識(shí)符、數(shù)字、字符串等。

標(biāo)識(shí)符、數(shù)字、字符串都很好理解和識(shí)別,但哪些字符應(yīng)該被識(shí)別為關(guān)鍵字、保留字,而不是標(biāo)識(shí)符呢?

此時(shí)可以閱讀 ECMAScript 規(guī)格了,ECMAScript 11.0 規(guī)格的 11.6.2 節(jié)介紹了關(guān)鍵詞和保留字列表。

關(guān)鍵詞(keywords)關(guān)鍵詞首先是標(biāo)識(shí)符,同時(shí)有語(yǔ)義,包括 if、while、async、await...,個(gè)別關(guān)鍵詞是可以用作變量名的。

保留字(reserved word)保留字首先是標(biāo)識(shí)符,但不能用作變量名。部分關(guān)鍵詞是保留字,但部分不是: if、while是保留字;await只有在 async方法和模塊中才是保留字;async不是保留字,它可以作為普通的變量名使用。

保留字列表

await
break
case
catch
class
const
continue
debugger
default
delete
do
else
enum
export
extends
false
finally
for
function
if
import
in
instanceof
new
null
returns
uper
switch
this
throw
true
try
typeof
var
void
while
with
yield

讀完上述規(guī)格,也就知道哪些字符單元是需要識(shí)別為保留字與關(guān)鍵詞,并高亮的了。

  • 識(shí)別全局對(duì)象,并高亮

繼續(xù)使用 babel-highlight 實(shí)現(xiàn)代碼塊中的全局對(duì)象高亮,那么,我們需要知道哪些是規(guī)格中描述的全局變量。

規(guī)格的 18 章介紹了全局對(duì)象,通過(guò)該章的描述,可以知道: 

全局屬性全局屬性有: globalThis、Infinity、NaN、undefined。

全局方法

全局方法有: eval(x)、isFinite、isNaN、parseFloat、parseInt、decodeURIComponent、encodeURIComponent 等。

  • 全局構(gòu)造函數(shù)

全局的構(gòu)造函數(shù)有: 

Array
ArrayBuffer
BigInt
BigInt64Array
BigUnit64Array
Boolean
DataView
Date
Error
EvalError
Float32Array
Float64Array
Function
Int8Array
Int16Array
Int32Array
Map
Number
Object
Promise
Proxy
RangeError
ReferenceError
RegExp
Set
SharedArrayBuffer
String
Symbol
SyntaxError
TypeError
Uint8Array
Uint8ClampedArray
Uint16Array
Uint32Array
URIError
WeakMap
WeakSet

其他的全局屬性Atomics、JSON、Math、Reflect。很顯然,當(dāng)字符單元的名稱(chēng)是上述名稱(chēng)中的一員時(shí),我們可以對(duì)其進(jìn)行高亮處理了(若上下文中無(wú)重新定義的同名變量)。

  • 自定義 Error

babel-loader 自身維護(hù)了私有的 LoaderError 對(duì)象,該對(duì)象繼承自原生 Error 類(lèi),并且訂制了部分實(shí)例屬性。代碼如下: 

class LoaderError extends Error {
    constructor(err) {
        super();


        const { name, message, codeFrame, hideStack } = format(err);


        this.name = "BabelLoaderError";


        this.message = ${name ? ${name}: ` : ""}${message}\n\n${codeFrame}\n`;


        this.hideStack = hideStack;


        Error.captureStackTrace(this, this.constructor);
    }
}

可以看到,babel-loader 自定義了錯(cuò)誤實(shí)例的 name、message、hideStack 屬性,那么,問(wèn)題是,原生的 Error 類(lèi)有哪些屬性和方法,哪些是開(kāi)發(fā)者可以自定義的呢?

規(guī)格的 19.5 章節(jié),詳細(xì)介紹了 Error 的各類(lèi)規(guī)范:

Error 作為函數(shù)被調(diào)用時(shí)(Error(...)),表現(xiàn)和 new Error(...) 一致,均會(huì)創(chuàng)建并返回 Error 的新實(shí)例

Error 可以被繼承,比如通過(guò) extends 的方式,子類(lèi)必須提供 constructor 方法,且該方法內(nèi)必須提供 super() 調(diào)用

Error 構(gòu)造函數(shù)必須有 prototype 屬性

Error.prototype 屬性需有以下屬性:

Error.prototype.constructor: 指向構(gòu)造函數(shù)

Error.prototype.message: 描述錯(cuò)誤信息,默認(rèn)是空字符串

Error.prototype.name: 描述錯(cuò)誤名稱(chēng),默認(rèn)值是 Error

從 LoaderError 的源碼可以看到,LoaderError 做了以下幾件事情:

  • LoaderError 繼承自 Error
  • 實(shí)例自定義了 name、message 屬性,明確 babel-loader 的信息
  • 實(shí)例自定義的 hideStack 屬性是非標(biāo)準(zhǔn)屬性,用于 babel-loader 內(nèi)部

web標(biāo)準(zhǔn)

是在解決 API Polyfil 的時(shí)候,Babel 配合使用的 core-js 除了提供 ECMAScript 標(biāo)準(zhǔn)下的 JavaScript API 實(shí)現(xiàn),也提供了 DOM/URL 等實(shí)現(xiàn)。而 DOM/URL 所屬的 web 標(biāo)準(zhǔn),由 W3C/WHATWG 制定。

圖片圖片

經(jīng)過(guò)多年發(fā)展,WHATWG 和 W3C 目前是合作關(guān)系,其中,WHATWG 維護(hù) HTML 和 DOM 標(biāo)準(zhǔn),W3C 使用 WHATWG 存儲(chǔ)庫(kù)中的 HTML 和 DOM 標(biāo)準(zhǔn)描述,W3C 在 HTML 部分的工作集中在 XHTML/XML 上。

圖片圖片

四、總結(jié)

本文介紹了 Babel 的概述/微內(nèi)核架構(gòu)/ECMAScript標(biāo)準(zhǔn)化方面的設(shè)計(jì)思想和部分實(shí)現(xiàn)原理。

上述內(nèi)容其實(shí)在很早之前就已經(jīng)成型了,筆者也查看了Babel最近的迭代內(nèi)容,發(fā)現(xiàn)并沒(méi)有太大的變化。至于代碼轉(zhuǎn)譯領(lǐng)域,目前是Babel還是其他工具哪個(gè)更有優(yōu)勢(shì),不在本文的討論范圍內(nèi)。除了比較社區(qū)哪些工具更好而言,“Babel的設(shè)計(jì)思路、其與標(biāo)準(zhǔn)規(guī)范是怎么配合的”等也是很值得學(xué)習(xí)的地方,也是這篇文章的產(chǎn)生背景。

責(zé)任編輯:武曉燕 來(lái)源: 得物技術(shù)
相關(guān)推薦

2021-05-19 07:56:26

Linux內(nèi)核搶占

2023-11-29 09:57:23

微服務(wù)容器

2017-08-15 13:05:58

Serverless架構(gòu)開(kāi)發(fā)運(yùn)維

2024-05-30 09:07:20

2023-06-07 15:34:21

架構(gòu)層次結(jié)構(gòu)

2021-12-09 08:09:31

Linux內(nèi)核臟頁(yè)

2021-07-26 07:47:36

數(shù)據(jù)庫(kù)

2023-11-13 16:33:46

2018-12-27 12:34:42

HadoopHDFS分布式系統(tǒng)

2018-04-16 11:04:23

HBaseRegion Serv數(shù)據(jù)庫(kù)

2022-01-14 12:28:18

架構(gòu)OpenFeign遠(yuǎn)程

2019-03-18 09:50:44

Nginx架構(gòu)服務(wù)器

2024-06-28 10:25:18

2010-06-01 15:25:27

JavaCLASSPATH

2016-12-08 15:36:59

HashMap數(shù)據(jù)結(jié)構(gòu)hash函數(shù)

2020-07-21 08:26:08

SpringSecurity過(guò)濾器

2021-07-02 06:54:44

Linux內(nèi)核主調(diào)度器

2021-07-05 06:51:45

Linux內(nèi)核調(diào)度器

2021-07-20 08:02:41

Linux進(jìn)程睡眠

2023-01-16 18:32:15

架構(gòu)APNacos
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)