国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

從 IIFE 聊到 Babel 帶你深入了解前端模塊化發展體系

tinylcy / 1336人閱讀

摘要:我覺得那時他可能并沒有料到,這一規則的制定會讓整個前端發生翻天覆地的變化。

前言

作為一名前端工程師,每天的清晨,你走進公司的大門,回味著前臺妹子的笑容,摘下耳機,泡上一杯茶,打開 Terminal 進入對應的項目目錄下,然后 npm run start / dev 或者 yarn start / dev 就開始了一天的工作。

當你需要進行時間的轉換只需要使用 dayjs 或者 momentjs, 當你需要封裝 http 請求的時候,你可以用 fetch 或者 axios, 當你需要做數據處理的時候,你可能會用 lodash 或者 underscore。

不知道你有沒有意識到,對于今天的我們而言,這些工具包讓開發效率得到了巨大的提升,但是這一切是從什么開始的呢?

這些就要從 Modular design (模塊化設計) 說起:

Modular design (模塊化設計)

在我剛接觸前端的時候,經常聽說 Modular design (模塊化設計) 這樣的術語,面試時也會經常被問到,“聊聊前端的模塊化”這樣的問題,或許很多人都可以說出幾個熟悉的名詞,甚至是他們之間的區別:

IIFE [Immediately Invoked Function Expression]

Common.js

AMD

CMD

ES6 Module

但就像你閱讀一個項目的源碼一樣,如果從第一個 commit 開始研究,那么你能收獲的或許不僅僅是,知道他們有什么區別,更重要的是,能夠知道在此之前的歷史中,是什么樣的原因,導致了區別于舊的規范而產生的新規范,并且基于這些,或許你能夠從中體會到這些改變意味著什么,甚至在將來的某個時刻,你也能成為這規則的制定者之一。

所以讓我們回到十年前,來看看是怎么實現模塊化設計的:

IIFE

IIFE 是 Immediately Invoked Function Expression 的縮寫,作為一個基礎知識,很多人可能都已經知道 IIFE 是怎么回事,(如果你已經掌握了 IIFE,可以跳過這節閱讀后面的內容) 但這里我們仍舊會解釋一下,它是怎么來的,因為在后面我們還會再次提到它:

最開始,我們對于模塊區分的概念,可能是從文件的區分開始的,在一個簡易的項目中,編程的習慣是通過一個 HTML 文件加上若干個 JavaScript 文件來區分不同的模塊,就像這樣:

我們可以通過這樣一個簡單的項目來說明,來看看每個文件里面的內容:

demo.html

這個文件,只是簡單的引入了其他的幾個 JavaScript 文件:

<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta http-equiv="X-UA-Compatible" content="ie=edge" />
  <title>demotitle>
head>
<script src="main.js">script>
<script src="header.js">script>
<script src="footer.js">script>

<body>body>

html>
其他三個 JavaScript 文件

在不同的 js 文件中我們定義了不同的變量,分別對應文件名:

var header = "這是一條頂部信息" //header.js
var main_message = "這是一條內容信息"   //main.js
var main_error = "這是一條錯誤信息"   //main.js
var footer = "這是一條底部信息" //footer.js

像這樣通過不同的文件來聲明變量的方式,實際上無法將這些變量區分開來。

它們都綁定在全局的 window / Global(node 環境下的全局變量) 對象上,嘗試去打印驗證一下:

這簡直就是一場噩夢,你可能沒有意識到這會導致什么嚴重的結果,我們試著在 footer.js 中對 header 變量進行賦值操作,讓我們在末尾加上這樣一行代碼:

header = "nothing"

打印后你就會發現,window.header 的已經被更改了:

試想一下,你永遠無法預料在什么時候什么地點無意中就改掉了之前定義的某個變量,如果這是在一個團隊中,這是一件多么可怕的事情。

Okay,現在我們知道,僅僅通過不同的文件,我們無法做到將這些變量分開,因為它們都被綁在了同一個 window 變量上。

但是更重要的是,怎么去解決呢?我們都知道,在 JavaScript 中,函數擁有自己的作用域 的,也就是說,如果我們可以用一個函數將這些變量包裹起來,那這些變量就不會直接被聲明在全局變量 window 上了:

所以現在 main.js 的內容會被修改成這樣:

function mainWarraper() {
  var main_message = "這是一條內容信息" //main.js
  var main_error = "這是一條錯誤信息" //main.js
  console.log("error:", main_error)
}

mainWarraper()

為了確保我們定義在函數 mainWarraper 的內容會被執行,所以我們必須在這里執行 mainWarraper() 本身,現在我們在 window 里面找不到 main_messagemain_error 了,因為它們被隱藏在了 mainWarraper 中,但是 mainWarraper 仍舊污染了我們的 window:

這個方案還不夠完美,怎么改進呢?

答案就是我們要說的 IIFE 我們可以定義一個 立即執行的匿名函數 來解決這個問題:

(function() {
  var main_message = "這是一條內容信息" //main.js
  var main_error = "這是一條錯誤信息" //main.js
  console.log("error:", main_error)
})()

因為是一個匿名的函數,執行完后很快就會被釋放,這種機制不會污染全局對象。

雖然看起來有些麻煩,但它確實解決了我們將變量分離開來的需求,不是嗎?然而在今天,幾乎沒有人會用這樣方式來實現模塊化編程。

后來又發生了什么呢?

CommonJS

在 2009 年的一個冬天, 一名來自 Mozilla 團隊的的工程師 Kevin Dangoor 開始搗鼓了一個叫 ServerJS 的項目,他是這樣描述的:

"What I’m describing here is not a technical problem. It’s a matter of people getting together and making a decision to step forward and start building up something bigger and cooler together."

"在這里我描述的不是一個技術問題。 這是一個關于大家齊心合力,做出決定向前邁進,并且開始一起建造一些更大更酷的東西的問題。"

這個項目在 2009 年的 8 月份更名為今日我們熟悉的 CommonJS 以顯示 API 更廣泛的適用性。我覺得那時他可能并沒有料到,這一規則的制定會讓整個前端發生翻天覆地的變化。

CommonJS 在 Wikipedia 中是這樣描述的:

CommonJS is a project with the goal to establish conventions on module ecosystem for JavaScript outside of the web browser. The primary reason of its creation was a major lack of commonly accepted form of JavaScript scripts module units which could be reusable in environments different from that provided by a conventional web browser e.g. web server or native desktop applications which run JavaScript scripts.

CommonJS 是一個旨在 Web 瀏覽器之外,為 JavaScript 建立模塊生態系統的約定的項目。 其創建的主要原因是缺乏普遍接受的 JavaScript 腳本模塊單元形式,而這一形式可以讓 JavaScript 在不同于傳統網絡瀏覽器提供的環境中重復使用,例如, 運行 JavaScript 腳本的 Web 服務器或本機桌面應用程序。

通過上面這些描述,相信你已經知道 CommonJS 是誕生于怎樣的背景,但是這里所說的 CommonJS 是一套通用的規范,與之對應的有非常多不同的實現:

圖片來源于 wiki

但是我們關注的是其中 Node.js 的實現部分。

Node.js Modules

這里不會解釋 Node.js Modules 的 API 基本用法,因為這些都可以通過閱讀 官方文檔 來了解,我們會討論為什么會這樣設計,以及大家比較難理解的點來展開。

在 Node.js 模塊系統中,每個文件都被視為一個多帶帶的模塊,在一個Node.js 的模塊中,本地的變量是私有的,而這個私有的實現,是通過把 Node.js 的模塊包裝在一個函數中,也就是 The module wrapper,我們來看看,在 官方示例中 它長什么樣:

(function(exports, require, module, __filename, __dirname) {
// Module code actually lives in here
// 實際上,模塊內的代碼被放在這里
});

是的,在模塊內的代碼被真正執行以前,實際上,這些代碼都被包含在了一個這樣的函數中。

如果你真正閱讀了上一節中關于 IIFE 的內容,你會發現,其實核心思想是一樣的,Node.js 對于模塊私有化的實現也還是通過了一個函數。但是這有哪些不同呢?

雖然這里有 5 個參數,但是我們把它們先放在一邊,然后嘗試站在一個模塊的角度來思考這樣一個問題:作為一個模塊,你希望自己具備什么樣的能力呢");

    暴露部分自己的方法或者變量的能力 :這是我存在的意義,因為,對于那些想使用我的人而言這是必須的。[ exports:導出對象 , module:模塊的引用 ]

    引入其他模塊的能力:有的時候我也需要通過別人的幫助來實現一些功能,只把我的注意力放在我想做的事情(核心邏輯)上。[ require:引用方法 ]

    告訴別人我的物理位置:方便別人找到我,并且對我進行更新或者修改。[ __filename:絕對文件名, __dirname:目錄路徑 ]

Node.js Modules 中 require 的實現

為什么我們要了解 require 方法的實現呢?因為理解這一過程,我們可以更好地理解下面的幾個問題:

    當我們引入一個模塊的時候,我們究竟做了怎樣一件事情?

    exportsmodule.exports 有什么聯系和區別?

    這樣的方式有什么弊端?

在文檔中,有簡易版的 require 的實現:

function require(/* ... */) {
  const module = { exports: {} };
  ((module, exports) => {
    // Module code here. In this example, define a function.
    // 模塊代碼在這里,在這個例子中,我們定義了一個函數
    function someFunc() {}
    exports = someFunc;
    // At this point, exports is no longer a shortcut to module.exports, and
    // this module will still export an empty default object.
    // 當代碼運行到這里時,exports 不再是 module.exports 的引用,并且當前的
    // module 仍舊會導出一個空對象(就像上面聲明的默認對象那樣)
    module.exports = someFunc;
    // At this point, the module will now export someFunc, instead of the
    // default object.
    // 當代碼運行到這時,當前 module 會導出 someFunc 而不是默認的對象
  })(module, module.exports);
  return module.exports;
}

回到剛剛提出的問題:

1. require 做了怎樣一件事情");

require 相當于把被引用的 module 拷貝了一份到當前 module 中

2. exportsmodule.exports 的聯系和區別?

代碼中的注釋以及 require 函數第一行默認值的聲明,很清楚的闡述了,exportsmodule.exports 的區別和聯系:

exportsmodule.exports 的引用。作為一個引用,如果我們修改它的值,實際上修改的是它對應的引用對象的值。

就如:

exports.a = 1
// 等同于
module.exports = {
    a: 1
}

但是如果我們修改了 exports 引用的地址,對于它原來所引用的內容來說,沒有任何影響,反而我們斷開了這個引用于原來的地址之間的聯系:

exports = {
    a: 1
}

// 相當于

let other = {a: 1} //為了更加直觀,我們這樣聲明了一個變量
exports = other

exports 從指向 module.exports 變為了 other。

3. 弊端

CommonJS 這一標準的初衷是為了讓 JavaScript 在多個環境下都實現模塊化,但是 Node.js 中的實現依賴了 Node.js 的環境變量:moduleexportsrequire,global,瀏覽器沒法用啊,所以后來出現了 Browserify 這樣的實現,但是這并不是本文要討論的內容,有興趣的同學可以讀讀阮一峰老師的 這篇文章。

說完了服務端的模塊化,接下來我們聊聊,在瀏覽器這一端的模塊化,又經歷了些什么呢?

RequireJS & AMD(Asynchronous Module Definition)

試想一下,假如我們現在是在瀏覽器環境下,使用類似于 Node.js Module 的方式來管理我們的模塊(例如 Browserify),會有什么樣的問題呢?

因為我們已經了解了 require() 的實現,所以你會發現這其實是一個復制的過程,將被 require 的內容,賦值到一個 module 對象的屬性上,然后返回這個對象的 exports 屬性。

這樣做會有什么問題呢?在我們還沒有完成復制的時候,無法使用被引用的模塊中的方法和屬性。在服務端可能這不是一個問題(因為服務器的文件都是存放在本地,并且是有緩存的),但在瀏覽器環境下,這會導致阻塞,使得我們后面的步驟無法進行下去,還可能會執行一個未定義的方法而導致出錯。

相對于服務端的模塊化,瀏覽器環境下,模塊化的標準必須滿足一個新的需求:異步的模塊管理

在這樣的背景下,RequireJS 出現了,我們簡單的了解一下它最核心的部分:

引入其他模塊: require()

定義新的模塊: define()

官方文檔中的使用的例子:

requirejs.config({
    // 默認加載 js/lib 路徑下的module ID
    baseUrl: "js/lib",
    // 除去 module ID 以 "app" 開頭的 module 會從 js/app 路徑下加載。
    // 關于 paths 的配置是與 baseURL 關聯的,并且因為 paths 可能會是一個目錄,
    // 所以不要使用 .js 擴展名 
    paths: {
        app: "../app"
    }
});

// 開始主邏輯
requirejs(["jquery", "canvas", "app/sub"],
function   ($,        canvas,   sub) {
    //jQuery, canvas 和 app/sub 模塊已經被加載并且可以在這里使用了。
});

官方文檔中的定義的例子:

// 簡單的對象定義
define({
    color: "black",
    size: "unisize"
});

// 當你需要一些邏輯來做準備工作時可以這樣定義:
define(function () {
    //這里可以做一些準備工作
    return {
        color: "black",
        size: "unisize"
    }
});

// 依賴于某些模塊來定義屬于你自己的模塊
define(["./cart", "./inventory"], function(cart, inventory) {
        //通過返回一個對象來定義你自己的模塊
        return {
            color: "blue",
            size: "large",
            addToCart: function() {
                inventory.decrement(this);
                cart.add(this);
            }
        }
    }
);
優勢

RequireJS 是基于 AMD 規范 實現的,那么相對于 Node.js 的 Module 它有什么優勢呢");

以函數的形式返回模塊的值,尤其是構造函數,可以更好的實現API 設計,Node 中通過 module.exports 來支持這個,但使用 "return function (){}" 會更清晰。 這意味著,我們不必通過處理 “module” 來實現 “module.exports”,它是一個更清晰的代碼表達式。

動態代碼加載(在AMD系統中通過require([],function(){})來完成)是一項基本要求。 CJS談到了, 有一些建議,但沒有完全囊括它。 Node 不支持這種需求,而是依賴于require("")的同步行為,這對于 Web 環境來說是不方便的。

Loader 插件非常有用,在基于回調的編程中,這有助于避免使用常見的嵌套大括號縮進。

選擇性地將一個模塊映射到從另一個位置加載,很方便的地提供了用于測試的模擬對象。

每個模塊最多只能有一個 IO 操作,而且應該是簡潔的。 Web 瀏覽器不能容忍從多個 IO 中來查找模塊。 這與現在 Node 中的多路徑查找相對,并且避免使用 package.json 的 “main” 屬性。 而只使用模塊名稱,基于項目位置來簡單的映射到一個位置的模塊名稱,不需要詳細配置的合理默認規則,但允許在必要時進行簡單配置。

最好的是,如果有一個 "opt-in" 可以用來調用,以便舊的 JS 代碼可以加入到新系統。

如果一個 JS 模塊系統無法提供上述功能,那么與 AMD 及其相關 API 相比,它將在回調需求,加載器插件和基于路徑的模塊 ID 等方面處于明顯的劣勢。

新的問題

通過上面的語法說明,我們會發現一個很明顯的問題,在使用 RequireJS 聲明一個模塊時,必須指定所有的依賴項 ,這些依賴項會被當做形參傳到 factory 中,對于依賴的模塊會提前執行(在 RequireJS 2.0 也可以選擇延遲執行),這被稱為:依賴前置。

這會帶來什么問題呢?

加大了開發過程中的難度,無論是閱讀之前的代碼還是編寫新的內容,也會出現這樣的情況:引入的另一個模塊中的內容是條件性執行的。

SeaJS & CMD(Common Module Definition)

針對 AMD 規范中可以優化的部分,CMD 規范 出現了,而 SeaJS 則作為它的具體實現之一,與 AMD 十分相似:

// AMD 的一個例子,當然這是一種極端的情況
define(["header", "main", "footer"], function(header, main, footer) { 
    if (xxx) {
      header.setHeader("new-title")
    }
    if (xxx) {
      main.setMain("new-content")
    }
    if (xxx) {
      footer.setFooter("new-footer")
    }
});

 // 與之對應的 CMD 的寫法
define(function(require, exports, module) {
    if (xxx) {
      var header = require("./header")
      header.setHeader("new-title")
    }
    if (xxx) {
      var main = require("./main")
      main.setMain("new-content")
    }
    if (xxx) {
      var footer = require("./footer")
      footer.setFooter("new-footer")
    }
});

我們可以很清楚的看到,CMD 規范中,只有當我們用到了某個外部模塊的時候,它才會去引入,這回答了我們上一小節中遺留的問題,這也是它與 AMD 規范最大的不同點:CMD推崇依賴就近 + 延遲執行

仍然存在的問題

我們能夠看到,按照 CMD 規范的依賴就近的規則定義一個模塊,會導致模塊的加載邏輯偏重,有時你并不知道當前模塊具體依賴了哪些模塊或者說這樣的依賴關系并不直觀。

而且對于 AMD 和 CMD 來說,都只是適用于瀏覽器端的規范,而 Node.js module 僅僅適用于服務端,都有各自的局限性。

ECMAScript6 Module

ECMAScript6 標準增加了 JavaScript 語言層面的模塊體系定義,作為瀏覽器和服務器通用的模塊解決方案它可以取代我們之前提到的 AMDCMD ,CommonJS。(在此之前還有一個 UMD(Universal Module Definition)規范也適用于前后端,但是本文不討論,有興趣可以查看 UMD文檔 )

關于 ES6 的 Module 相信大家每天的工作中都會用到,對于使用上有疑問可以看看 ES6 Module 入門,阮一峰,當然你也可以查看 TC39的官方文檔

為什么要在標準中添加模塊體系的定義呢?引用文檔中的一句話:

"The goal for ECMAScript 6 modules was to create a format that both users of CommonJS and of AMD are happy with"

"ECMAScript 6 modules 的目標是創造一個讓 CommonJS 和 AMD 用戶都滿意的格式"

它憑借什么做到這一點呢?

與 CommonJS 一樣,具有緊湊的語法,對循環依賴以及單個 exports 的支持。

與 AMD 一樣,直接支持異步加載和可配置模塊加載。

除此之外,它還有更多的優勢:

語法比CommonJS更緊湊。

結構可以靜態分析(用于靜態檢查,優化等)。

對循環依賴的支持比 CommonJS 好。

注意這里的描述里出現了兩個詞 循環依賴靜態分析,我們在后面會深入討論。首先我們來看看, TC39 的 官方文檔 中定義的 ES6 modules 規范是什么。

深入 ES6 Module 規范

在 15.2.1.15 節 中,定義了 Abstract Module Records (抽象的模塊記錄) 的 Module Record Fields (模塊記錄字段) 和 Abstract Methods of Module Records (模塊記錄的抽象方法)

Module Record Fields 模塊記錄字段

Field Name(字段名) Value Type(值類型) Meaning(含義)
[[Realm]] 域 Realm Record | undefined The Realm within which this module was created. undefined if not yet assigned.

將在其中創建當前模塊,如果模塊未聲明則為 undefined。

[[Environment]] 環境 Lexical Environment | undefined The Lexical Environment containing the top level bindings for this module. This field is set when the module is instantiated.

詞法環境包含當前模塊的頂級綁定。 在實例化模塊時會設置此字段。

[[Namespace]] 命名空間 Object | undefined The Module Namespace Object if one has been created for this module. Otherwise undefined.

模塊的命名空間對象(如果已為此模塊創建了一個)。 否則為 undefined。

[[Evaluated]] 執行結束 Boolean Initially false, true if evaluation of this module has started. Remains true when evaluation completes, even if it is an abrupt completion

初始值為 false 當模塊開始執行時變成 true 并且持續到執行結束,哪怕是突然的終止(突然的終止,會有很多種原因,如果對原因感興趣可以看下 這個回答)

Abstract Methods of Module Records 模塊記錄的抽象方法

Method 方法 Purpose 目的
GetExportedNames(exportStarSet) Return a list of all names that are either directly or indirectly exported from this module.

返回一個從此模塊直接或間接導出的所有名稱的列表。

ResolveExport(exportName, resolveSet, exportStarSet)

Return the binding of a name exported by this modules. Bindings are represented by a Record of the form {[[module]]: Module Record, [[bindingName]]: String}.

返回此模塊導出的名稱的綁定。 綁定由此形式的記錄表示:{[[module]]: Module Record, [[bindingName]]: String}

ModuleDeclarationInstantiation()

Transitively resolve all module dependencies and create a module Environment Record for the module.

傳遞性地解析所有模塊依賴關系,并為模塊創建一個環境記錄

ModuleEvaluation()

Do nothing if this module has already been evaluated. Otherwise, transitively evaluate all module dependences of this module and then evaluate this module.

如果此模塊已經被執行過,則不執行任何操作。 否則,傳遞執行此模塊的所有模塊依賴關系,然后執行此模塊。

ModuleDeclarationInstantiation must be completed prior to invoking this method.

ModuleDeclarationInstantiation 必須在調用此方法之前完成

也就是說,一個最最基礎的模塊,至少應該包含上面這些字段,和方法。反復閱讀后你會發現,其實這里只是告知了一個最基礎的模塊,應該包含某些功能的方法,或者定義了模塊的格式,但是在我們具體實現的時候,就像原文中說的一樣:

An implementation may parse a sourceText as a Module, analyze it for Early Error conditions, and instantiate it prior to the execution of the TopLevelModuleEvaluationJob for that sourceText.

實現可以是:將 sourceText 解析為模塊,對其進行早期錯誤條件分析,并在執行TopLevelModuleEvaluationJob之前對其進行實例化。

An implementation may also resolve, pre-parse and pre-analyze, and pre-instantiate module dependencies of sourceText. However, the reporting of any errors detected by these actions must be deferred until the TopLevelModuleEvaluationJob is actually executed.

實現還可以是:解析,預解析和預分析,并預先實例化 sourceText 的模塊依賴性。 但是,必須將這些操作檢測到的任何錯誤,推遲到實際執行TopLevelModuleEvaluationJob 之后再報告出來。

通過這些我們只能得出一個結論,在具體實現的時候,只有第一步是固定的,也就是:

解析:如 ParseModule 這一節中所介紹的一樣,首先會對模塊的源代碼進行語法錯誤檢查。例如 early-errors,如果解析失敗,讓 body 報出一個或多個解析錯誤和/或早期錯誤。如果解析成功并且沒有找到早期錯誤,則將 body 作為生成的解析樹繼續執行,最后返回一個 Source Text Module Records

那后面會發生什么呢?我們可以通過閱讀具體實現的源碼來分析。

從 babel-helper-module-transforms 來看 ES6 module 實現

Babel 作為 ES6 官方指定的編譯器,在如今的前端開發中發揮著巨大的作用,它可以幫助我們將開發人員書寫的 ES6 語法的代碼轉譯為 ES5 的代碼然后交給 JS 引擎去執行,這一行為讓我們可以毫無顧忌的使用 ES6 給我們帶來的方便。

這里我們就以 Babel 中 babel-helper-module-transforms 的具體實現,來看看它是如何實現 ES6 module 轉換的步驟

在這里我不會逐行的去分析源碼,而是從結構和調用上來看具體的邏輯

首先我們羅列一下這個文件中出現的所有方法(省略掉方法體和參數)

/**
 * Perform all of the generic ES6 module rewriting needed to handle initial
 * module processing. This function will rewrite the majority of the given
 * program to reference the modules described by the returned metadata,
 * and returns a list of statements for use when initializing the module.
 * 執行處理初始化所需的所有通用ES6模塊重寫
 * 模塊處理。 這個函數將重寫給定的大部分
 * 程序引用返回的元數據描述的模塊,
 * 并返回初始化模塊時使用的語句列表。
 */
export function rewriteModuleStatementsAndPrepareHeader() {...}

/**
 * Flag a set of statements as hoisted above all else so that module init
 * statements all run before user code.
 * 將一組語句標記為高于其他所有語句,以便模塊初始化
?* 語句全部在用戶代碼之前運行。
 */
export function ensureStatementsHoisted() {...}
/**
 * Given an expression for a standard import object, like "require("foo")",
 * wrap it in a call to the interop helpers based on the type.
 * 給定標準導入對象的表達式,如“require("foo")”,
?* 根據類型將其包裝在對 interop 助手的調用中。
 */
export function wrapInterop() {...}

/**
 * Create the runtime initialization statements for a given requested source.
 * These will initialize all of the runtime import/export logic that
 * can"t be handled statically by the statements created by
 * 為給定的請求源創建運行時初始化語句。
?* 這些將初始化所有運行時導入/導出邏輯
?* 不能由創建的語句靜態處理
 * buildExportInitializationStatements().
 */
export function buildNamespaceInitStatements() {...}


/**
 * Build an "__esModule" header statement setting the property on a given object.
 * 構建一個“__esModule”頭語句,在給定對象上設置屬性
 */
function buildESModuleHeader() {...}


/**
 * Create a re-export initialization loop for a specific imported namespace.
 * 為特定導入的命名空間,創建 重新導出 初始化循環。
 */
function buildNamespaceReexport() {...}
/**
 * Build a statement declaring a variable that contains all of the exported
 * variable names in an object so they can easily be referenced from an
 * export * from statement to check for conflicts.
 * 構建一個聲明,聲明包含對象中所有導出變量名稱的變量的語句,以便可以從export * from語句中輕松引用它們以檢查沖突。
 */
function buildExportNameListDeclaration() {...}

/**
 * Create a set of statements that will initialize all of the statically-known
 * export names with their expected values.
 * 創建一組將通過預期的值來初始化 所有靜態已知的導出名的語句
 */
function buildExportInitializationStatements() {...}

/**
 * Given a set of export names, create a set of nested assignments to
 * initialize them all to a given expression.
 * 給定一組 export names,創建一組嵌套分配將它們全部初始化為給定的表達式。
 */
function buildInitStatement() {...}

然后我們來看看他們的調用關系:

我們以 A -> B 的形式表示在 A 中調用了 B

    buildNamespaceInitStatements:為給定的請求源創建運行時初始化語句。這些將初始化所有運行時導入/導出邏輯

    rewriteModuleStatementsAndPrepareHeader 所有通用ES6模塊重寫,以引用返回的元數據描述的模塊。
    -> buildExportInitializationStatements創建所有靜態已知的名稱的 exports
    -> buildInitStatement 給定一組 export names,創建一組嵌套分配將它們全部初始化為給定的表達式。

所以總結一下,加上前面我們已知的第一步,其實后面的步驟分為兩部分:

    解析:首先會對模塊的源代碼進行語法錯誤檢查。例如 early-errors,如果解析失敗,讓 body 報出一個或多個解析錯誤和/或早期錯誤。如果解析成功并且沒有找到早期錯誤,則將 body 作為生成的解析樹繼續執行,最后返回一個 Source Text Module Records

    初始化所有運行時導入/導出邏輯

    以引用返回的元數據描述的模塊,并且用一組 export names 將所有靜態的 exports 初始化為指定的表達式。

到這里其實我們已經可以很清晰的知道,在 編譯階段 ,我們一段 ES6 module 中的代碼經歷了什么:

ES6 module 源碼 -> Babel 轉譯-> 一段可以執行的代碼

也就是說直到編譯結束,其實我們模塊內部的代碼都只是被轉換成了一段靜態的代碼,只有進入到 運行時 才會被執行。

這也就讓 靜態分析 有了可能。

最后

本文我們從 JavaScript Module 的發展史開始聊起,一直聊到了如今與我們息息相關的 ES6 代碼的編譯,很感謝前人走出的這些道路,讓如今我這樣的普通人也能夠進入到編程的世界,也不得不感嘆,一個問題越深究,才會發現其中并不簡單。

感謝那些能夠耐心讀到這里的人,因為這篇文章前前后后,也花了4天的時間來研究,時常感嘆有價值的資料實在太少了。

下一篇我們會接著聊聊靜態分析,和循環引用

我是 Dendoink ,奇舞周刊原創作者,掘金 [聯合編輯 / 小冊作者] 。

對于技術人而言: 是單兵作戰能力, 則是運用能力的方法。得心應手,出神入化就是 。在前端娛樂圈,我想成為一名出色的人民藝術家。

掃一掃關注公眾號 [ 前端惡霸 ] ,我在這里等你:

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/7378.html

相關文章

  • 優秀文章收藏(慢慢消化)持續更新~

    摘要:整理收藏一些優秀的文章及大佬博客留著慢慢學習原文協作規范中文技術文檔協作規范阮一峰編程風格凹凸實驗室前端代碼規范風格指南這一次,徹底弄懂執行機制一次弄懂徹底解決此類面試問題瀏覽器與的事件循環有何區別筆試題事件循環機制異步編程理解的異步 better-learning 整理收藏一些優秀的文章及大佬博客留著慢慢學習 原文:https://www.ahwgs.cn/youxiuwenzhan...

    JeOam 評論0 收藏0
  • 使用rollup構建你的JavaScript項目【一】

    摘要:瀏覽器使用編譯成一個自執行函數,可以直接在中的標簽直接引入使用編譯成模塊瀏覽器和通用模式需要設置一個大報名使用配置文件,來一個項目開始之前,先在本地創建一個項目,并在根目錄通過創建一個文件,構建一個用來管理依賴的項目。 什么是Rollup? 前端項目工程化構建工具也發展好幾年了,生態演化,慢慢發展出了很多好的構建項目的工具,從最開始的grunt,gulp到webpack,前端的工程化越...

    meislzhua 評論0 收藏0
  • 1 到完美,寫一個 js 庫、node 庫、前端組件庫

    摘要:從到完美,寫一個庫庫前端組件庫之前講了很多關于項目工程化前端架構前端構建等方面的技術,這次說說怎么寫一個完美的第三方庫。使用導出模塊,就可以在使用這個庫的項目中構建時使用功能。 從 1 到完美,寫一個 js 庫、node 庫、前端組件庫 之前講了很多關于項目工程化、前端架構、前端構建等方面的技術,這次說說怎么寫一個完美的第三方庫。 1. 選擇合適的規范來寫代碼 js 模塊化的發展大致有...

    rollback 評論0 收藏0
  • 1 到完美,寫一個 js 庫、node 庫、前端組件庫

    摘要:從到完美,寫一個庫庫前端組件庫之前講了很多關于項目工程化前端架構前端構建等方面的技術,這次說說怎么寫一個完美的第三方庫。使用導出模塊,就可以在使用這個庫的項目中構建時使用功能。 從 1 到完美,寫一個 js 庫、node 庫、前端組件庫 之前講了很多關于項目工程化、前端架構、前端構建等方面的技術,這次說說怎么寫一個完美的第三方庫。 1. 選擇合適的規范來寫代碼 js 模塊化的發展大致有...

    xiaolinbang 評論0 收藏0
  • 手把手帶你走進下一代的ES6模塊打包工具—Rollup

    摘要:首先把這個示例倉庫下載到本地準備就緒,正文開始簡介以下內容基于和這兩個打包工具來展開。但是目前,中的大多數包都是以模塊的形式出現的。在它們更改之前,我們需要將模塊轉換為供處理??梢栽谥邪炎⑨尩艨纯创虬蟮奈募瑫颜麄€打包進來。 本文一共七個例子,由淺入深帶你熟悉Rollup。首先把 rollup-demos 這個示例倉庫下載到本地 mkdir rollup cd rollup git...

    李文鵬 評論0 收藏0

發表評論

0條評論

tinylcy

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<