成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

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

tinylcy / 1349人閱讀

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

前言

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

當(dāng)你需要進(jìn)行時間的轉(zhuǎn)換只需要使用 dayjs 或者 momentjs, 當(dāng)你需要封裝 http 請求的時候,你可以用 fetch 或者 axios, 當(dāng)你需要做數(shù)據(jù)處理的時候,你可能會用 lodash 或者 underscore

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

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

Modular design (模塊化設(shè)計)

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

IIFE [Immediately Invoked Function Expression]

Common.js

AMD

CMD

ES6 Module

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

所以讓我們回到十年前,來看看是怎么實現(xiàn)模塊化設(shè)計的:

IIFE

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

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

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

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 文件中我們定義了不同的變量,分別對應(yīng)文件名:

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

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

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

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

header = "nothing"

打印后你就會發(fā)現(xiàn),window.header 的已經(jīng)被更改了:

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

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

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

所以現(xiàn)在 main.js 的內(nèi)容會被修改成這樣:

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

mainWarraper()

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

這個方案還不夠完美,怎么改進(jìn)呢?

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

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

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

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

后來又發(fā)生了什么呢?

CommonJS

在 2009 年的一個冬天, 一名來自 Mozilla 團(tuán)隊的的工程師 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."

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

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

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 建立模塊生態(tài)系統(tǒng)的約定的項目。 其創(chuàng)建的主要原因是缺乏普遍接受的 JavaScript 腳本模塊單元形式,而這一形式可以讓 JavaScript 在不同于傳統(tǒng)網(wǎng)絡(luò)瀏覽器提供的環(huán)境中重復(fù)使用,例如, 運行 JavaScript 腳本的 Web 服務(wù)器或本機(jī)桌面應(yīng)用程序。

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

圖片來源于 wiki

但是我們關(guān)注的是其中 Node.js 的實現(xiàn)部分。

Node.js Modules

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

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

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

是的,在模塊內(nèi)的代碼被真正執(zhí)行以前,實際上,這些代碼都被包含在了一個這樣的函數(shù)中。

如果你真正閱讀了上一節(jié)中關(guān)于 IIFE 的內(nèi)容,你會發(fā)現(xiàn),其實核心思想是一樣的,Node.js 對于模塊私有化的實現(xiàn)也還是通過了一個函數(shù)。但是這有哪些不同呢?

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

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

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

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

Node.js Modules 中 require 的實現(xiàn)

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

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

    exportsmodule.exports 有什么聯(lián)系和區(qū)別?

    這樣的方式有什么弊端?

在文檔中,有簡易版的 require 的實現(xiàn):

function require(/* ... */) {
  const module = { exports: {} };
  ((module, exports) => {
    // Module code here. In this example, define a function.
    // 模塊代碼在這里,在這個例子中,我們定義了一個函數(shù)
    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.
    // 當(dāng)代碼運行到這里時,exports 不再是 module.exports 的引用,并且當(dāng)前的
    // module 仍舊會導(dǎo)出一個空對象(就像上面聲明的默認(rèn)對象那樣)
    module.exports = someFunc;
    // At this point, the module will now export someFunc, instead of the
    // default object.
    // 當(dāng)代碼運行到這時,當(dāng)前 module 會導(dǎo)出 someFunc 而不是默認(rèn)的對象
  })(module, module.exports);
  return module.exports;
}

回到剛剛提出的問題:

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

require 相當(dāng)于把被引用的 module 拷貝了一份到當(dāng)前 module 中

2. exportsmodule.exports 的聯(lián)系和區(qū)別?

代碼中的注釋以及 require 函數(shù)第一行默認(rèn)值的聲明,很清楚的闡述了,exportsmodule.exports 的區(qū)別和聯(lián)系:

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

就如:

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

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

exports = {
    a: 1
}

// 相當(dāng)于

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

exports 從指向 module.exports 變?yōu)榱?other

3. 弊端

CommonJS 這一標(biāo)準(zhǔn)的初衷是為了讓 JavaScript 在多個環(huán)境下都實現(xiàn)模塊化,但是 Node.js 中的實現(xiàn)依賴了 Node.js 的環(huán)境變量:module,exports,require,global,瀏覽器沒法用啊,所以后來出現(xiàn)了 Browserify 這樣的實現(xiàn),但是這并不是本文要討論的內(nèi)容,有興趣的同學(xué)可以讀讀阮一峰老師的 這篇文章。

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

RequireJS & AMD(Asynchronous Module Definition)

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

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

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

相對于服務(wù)端的模塊化,瀏覽器環(huán)境下,模塊化的標(biāo)準(zhǔn)必須滿足一個新的需求:異步的模塊管理

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

引入其他模塊: require()

定義新的模塊: define()

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

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

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

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

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

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

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

RequireJS 是基于 AMD 規(guī)范 實現(xiàn)的,那么相對于 Node.js 的 Module 它有什么優(yōu)勢呢");

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

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

Loader 插件非常有用,在基于回調(diào)的編程中,這有助于避免使用常見的嵌套大括號縮進(jìn)。

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

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

最好的是,如果有一個 "opt-in" 可以用來調(diào)用,以便舊的 JS 代碼可以加入到新系統(tǒng)。

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

新的問題

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

這會帶來什么問題呢?

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

SeaJS & CMD(Common Module Definition)

針對 AMD 規(guī)范中可以優(yōu)化的部分,CMD 規(guī)范 出現(xiàn)了,而 SeaJS 則作為它的具體實現(xiàn)之一,與 AMD 十分相似:

// AMD 的一個例子,當(dāng)然這是一種極端的情況
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")
    }
});

 // 與之對應(yīng)的 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 規(guī)范中,只有當(dāng)我們用到了某個外部模塊的時候,它才會去引入,這回答了我們上一小節(jié)中遺留的問題,這也是它與 AMD 規(guī)范最大的不同點:CMD推崇依賴就近 + 延遲執(zhí)行

仍然存在的問題

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

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

ECMAScript6 Module

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

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

為什么要在標(biāo)準(zhǔn)中添加模塊體系的定義呢?引用文檔中的一句話:

"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 的目標(biāo)是創(chuàng)造一個讓 CommonJS 和 AMD 用戶都滿意的格式"

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

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

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

除此之外,它還有更多的優(yōu)勢:

語法比CommonJS更緊湊。

結(jié)構(gòu)可以靜態(tài)分析(用于靜態(tài)檢查,優(yōu)化等)。

對循環(huán)依賴的支持比 CommonJS 好。

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

深入 ES6 Module 規(guī)范

在 15.2.1.15 節(jié) 中,定義了 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.

將在其中創(chuàng)建當(dāng)前模塊,如果模塊未聲明則為 undefined。

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

詞法環(huán)境包含當(dāng)前模塊的頂級綁定。 在實例化模塊時會設(shè)置此字段。

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

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

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

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

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.

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

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

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

ModuleDeclarationInstantiation()

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

傳遞性地解析所有模塊依賴關(guān)系,并為模塊創(chuàng)建一個環(huán)境記錄

ModuleEvaluation()

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

如果此模塊已經(jīng)被執(zhí)行過,則不執(zhí)行任何操作。 否則,傳遞執(zhí)行此模塊的所有模塊依賴關(guān)系,然后執(zhí)行此模塊。

ModuleDeclarationInstantiation must be completed prior to invoking this method.

ModuleDeclarationInstantiation 必須在調(diào)用此方法之前完成

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

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.

實現(xiàn)可以是:將 sourceText 解析為模塊,對其進(jìn)行早期錯誤條件分析,并在執(zhí)行TopLevelModuleEvaluationJob之前對其進(jìn)行實例化。

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.

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

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

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

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

從 babel-helper-module-transforms 來看 ES6 module 實現(xiàn)

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

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

在這里我不會逐行的去分析源碼,而是從結(jié)構(gòu)和調(diào)用上來看具體的邏輯

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

/**
 * 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.
 * 執(zhí)行處理初始化所需的所有通用ES6模塊重寫
 * 模塊處理。 這個函數(shù)將重寫給定的大部分
 * 程序引用返回的元數(shù)據(jù)描述的模塊,
 * 并返回初始化模塊時使用的語句列表。
 */
export function rewriteModuleStatementsAndPrepareHeader() {...}

/**
 * Flag a set of statements as hoisted above all else so that module init
 * statements all run before user code.
 * 將一組語句標(biāo)記為高于其他所有語句,以便模塊初始化
?* 語句全部在用戶代碼之前運行。
 */
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.
 * 給定標(biāo)準(zhǔn)導(dǎo)入對象的表達(dá)式,如“require("foo")”,
?* 根據(jù)類型將其包裝在對 interop 助手的調(diào)用中。
 */
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
 * 為給定的請求源創(chuàng)建運行時初始化語句。
?* 這些將初始化所有運行時導(dǎo)入/導(dǎo)出邏輯
?* 不能由創(chuàng)建的語句靜態(tài)處理
 * buildExportInitializationStatements().
 */
export function buildNamespaceInitStatements() {...}


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


/**
 * Create a re-export initialization loop for a specific imported namespace.
 * 為特定導(dǎo)入的命名空間,創(chuàng)建 重新導(dǎo)出 初始化循環(huán)。
 */
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.
 * 構(gòu)建一個聲明,聲明包含對象中所有導(dǎo)出變量名稱的變量的語句,以便可以從export * from語句中輕松引用它們以檢查沖突。
 */
function buildExportNameListDeclaration() {...}

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

/**
 * Given a set of export names, create a set of nested assignments to
 * initialize them all to a given expression.
 * 給定一組 export names,創(chuàng)建一組嵌套分配將它們?nèi)砍跏蓟癁榻o定的表達(dá)式。
 */
function buildInitStatement() {...}

然后我們來看看他們的調(diào)用關(guān)系:

我們以 A -> B 的形式表示在 A 中調(diào)用了 B

    buildNamespaceInitStatements:為給定的請求源創(chuàng)建運行時初始化語句。這些將初始化所有運行時導(dǎo)入/導(dǎo)出邏輯

    rewriteModuleStatementsAndPrepareHeader 所有通用ES6模塊重寫,以引用返回的元數(shù)據(jù)描述的模塊。
    -> buildExportInitializationStatements創(chuàng)建所有靜態(tài)已知的名稱的 exports
    -> buildInitStatement 給定一組 export names,創(chuàng)建一組嵌套分配將它們?nèi)砍跏蓟癁榻o定的表達(dá)式。

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

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

    初始化所有運行時導(dǎo)入/導(dǎo)出邏輯

    以引用返回的元數(shù)據(jù)描述的模塊,并且用一組 export names 將所有靜態(tài)的 exports 初始化為指定的表達(dá)式。

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

ES6 module 源碼 -> Babel 轉(zhuǎn)譯-> 一段可以執(zhí)行的代碼

也就是說直到編譯結(jié)束,其實我們模塊內(nèi)部的代碼都只是被轉(zhuǎn)換成了一段靜態(tài)的代碼,只有進(jìn)入到 運行時 才會被執(zhí)行。

這也就讓 靜態(tài)分析 有了可能。

最后

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

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

下一篇我們會接著聊聊靜態(tài)分析,和循環(huán)引用

我是 Dendoink ,奇舞周刊原創(chuàng)作者,掘金 [聯(lián)合編輯 / 小冊作者] 。

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

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

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/7378.html

相關(guān)文章

  • 優(yōu)秀文章收藏(慢慢消化)持續(xù)更新~

    摘要:整理收藏一些優(yōu)秀的文章及大佬博客留著慢慢學(xué)習(xí)原文協(xié)作規(guī)范中文技術(shù)文檔協(xié)作規(guī)范阮一峰編程風(fēng)格凹凸實驗室前端代碼規(guī)范風(fēng)格指南這一次,徹底弄懂執(zhí)行機(jī)制一次弄懂徹底解決此類面試問題瀏覽器與的事件循環(huán)有何區(qū)別筆試題事件循環(huán)機(jī)制異步編程理解的異步 better-learning 整理收藏一些優(yōu)秀的文章及大佬博客留著慢慢學(xué)習(xí) 原文:https://www.ahwgs.cn/youxiuwenzhan...

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

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

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

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

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

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

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

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

    李文鵬 評論0 收藏0

發(fā)表評論

0條評論

tinylcy

|高級講師

TA的文章

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