頁面是如何加載ES6 Modules

什么是模塊

模塊就是javascript文件以不同方式去加載(這個(gè)就和scripts的方式相反了,scripts是以原始的javascript工作方式加載的)。這種不同的模式很有必要,并且他們代表的語義也是不一樣的:

  • 模塊模式會(huì)自動(dòng)運(yùn)行在嚴(yán)格模式下,沒有辦法選擇。
  • 模塊頂層創(chuàng)建的變量不會(huì)共享到全局范圍,僅在模塊的范圍內(nèi)。
  • 模塊的頂層的this值是undefined。
  • 模塊不允許在代碼中使用HTML樣式的注釋(JavaScript早期瀏覽器時(shí)代的遺留功能)。
  • 模塊必須導(dǎo)出模塊外部代碼可用的任何內(nèi)容。
  • 模塊可以從其他模塊導(dǎo)入綁定

這些差異乍一看似乎很小,但它們代表了JavaScript代碼加載和評(píng)估方式的重大變化,下面會(huì)進(jìn)行介紹。模塊的真正強(qiáng)大之處在于能夠僅導(dǎo)出和導(dǎo)入所需的內(nèi)容,而不是文件中的所有內(nèi)容。

Basic Exporting

使用export把外部需要的內(nèi)容導(dǎo)出。在最簡(jiǎn)單的情況下,將export放在任何變量,函數(shù)或類聲明的前面,這樣從模塊中導(dǎo)出它。

export var name = 'my name'
export const say = () => 'your name'

// this function is private to the module
function subtract(num1, num2) {
    return num1 - num2;
}

// define a function...
function multiply(num1, num2) {
    return num1 * num2;
}

// ...and then export it later
export { multiply };

可以看出,每個(gè)導(dǎo)出都有個(gè)名稱,除非你導(dǎo)出默認(rèn)(后面會(huì)說)。否則沒法子使用這個(gè)語法導(dǎo)出匿名函數(shù)或類。當(dāng)然也可以聲明之后再導(dǎo)出。

Basic Importing

import { identifier1, identifier2 } from "./example.js";

這個(gè)看起來類似于解構(gòu)對(duì)象,但它不是。
當(dāng)你從module import的時(shí)候,他的行為就像const一樣。這意味著無法定義具有相同名稱的另一個(gè)變量(包括導(dǎo)入同名的另一個(gè)export),在import語句之前使用標(biāo)識(shí)符,或更改值。

Importing All of a Module

import * as example from "./example.js";

這里值得一提的是,無論在import語句中使用模塊多少次,模塊都只執(zhí)行一次。在導(dǎo)入模塊的代碼執(zhí)行之后,實(shí)例化的模塊保存在內(nèi)存中,并在另一個(gè)import語句引用它時(shí)重用。

import { sum } from "./example.js";
import { multiply } from "./example.js";
import { magicNumber } from "./example.js";

盡管這里使用了多次module,但是只會(huì)加載一次。如果同一應(yīng)用程序中的其他模塊要從example.js導(dǎo)入,那么這些模塊將使用此代碼使用的相同模塊實(shí)例。

模塊語法限制
導(dǎo)出和導(dǎo)入的一個(gè)重要限制是它們必須在其他語句和函數(shù)之外使用。

   if (flag) {
       export flag;    // syntax error
  }

exportif語句里,這是不被允許的。export是不可以有條件的,也不可以以其他的方式動(dòng)態(tài)export。這是為了靜態(tài)的確定需要導(dǎo)出的內(nèi)容。
同樣的,import也存在這個(gè)限制:

function tryImport() {
   import flag from "./example.js";    // syntax error
}

你不可以動(dòng)態(tài)的導(dǎo)入模塊就像你不能動(dòng)態(tài)的導(dǎo)出模塊那樣。導(dǎo)出和導(dǎo)入關(guān)鍵字設(shè)計(jì)為靜態(tài),因此文本編輯器等工具可以輕松地告知模塊中可用的信息。

A Subtle Quirk of Imported Bindings

這個(gè)是有些意思,你可以在導(dǎo)出的內(nèi)部更改,但是導(dǎo)出之后,就不可以更改了。

# export.js
export var name = "Nicholas";
export function setName(newName) {
    name = newName;
}

# import.js
import { name, setName } from "./example.js";

console.log(name);       // "Nicholas"
setName("Greg");
console.log(name);       // "Greg"

name = "Nicholas";       // error

Renaming Exports and Imports

這個(gè)就是通過as來改名字。

#export.js
function sum(num1, num2) {
    return num1 + num2;
}

export { sum as add };

#import.js
import { add as sum } from "./example.js";
console.log(typeof add);            // "undefined"
console.log(sum(1, 2));             // 3

Exporting Default Values

這個(gè)和上面的差不多,就是在導(dǎo)出的時(shí)候加上default關(guān)鍵字。大同小異吧。

Importing Without Bindings

某些模塊可能不會(huì)導(dǎo)出任何內(nèi)容,而只是對(duì)全局范圍內(nèi)的對(duì)象進(jìn)行修改。盡管模塊內(nèi)的頂級(jí)變量,函數(shù)和類不會(huì)自動(dòng)結(jié)束于全局范圍,但這并不意味著模塊無法訪問全局范圍。
可以在模塊內(nèi)部訪問內(nèi)置對(duì)象(如ArrayObject)的共享定義,對(duì)這些對(duì)象的更改將反映在其他模塊中。

#export.js
// module code without exports or imports
Array.prototype.pushAll = function(items) {

    // items must be an array
    if (!Array.isArray(items)) {
        throw new TypeError("Argument must be an array.");
    }

    // use built-in push() and spread operator
    return this.push(...items);
};

#import.js
import "./export.js";

let colors = ["red", "green", "blue"];
let items = [];

items.pushAll(colors);

像這種沒有具體內(nèi)容的導(dǎo)出,多用于墊片之類的功能。

Loading Modules

雖然ECMAScript 6定義了模塊的語法,但它沒有定義如何加載它們。這是規(guī)范的復(fù)雜性的一部分,該規(guī)范應(yīng)該與實(shí)現(xiàn)環(huán)境無關(guān)。ECMAScript 6不是嘗試創(chuàng)建適用于所有JavaScript環(huán)境的單一規(guī)范,而是僅指定語法并將加載機(jī)制抽象為未定義的內(nèi)部操作HostResolveImportedModule。Web瀏覽器和Node.js將決定如何以對(duì)各自環(huán)境有意義的方式實(shí)現(xiàn)HostResolveImportedModule`。

Using Modules in Web Browsers

ECMAScript 6之前,Web瀏覽器就有多種方法可以在Web應(yīng)用程序中包含JavaScript。腳本加載有:

  1. 使用帶有src屬性的<script>元素加載JavaScript代碼文件,該屬性指定加載代碼的位置。
  2. 使用沒有src屬性的<script>元素嵌入JavaScript代碼。
  3. 加載JavaScript代碼文件以作為worker(例如web workerservice worker)執(zhí)行。

為了完全支持模塊,Web瀏覽器必須更新每個(gè)機(jī)制。這些細(xì)節(jié)在HTML規(guī)范中定義,在這里對(duì)它們進(jìn)行總結(jié)。

Using Modules With <script>

<script>元素的默認(rèn)行為是將JavaScript文件作為腳本(而不是模塊)加載。缺少type屬性或type屬性包含JavaScript內(nèi)容類型(例如“text / javascript”)時(shí)會(huì)發(fā)生這種情況。然后,<script>元素可以執(zhí)行內(nèi)聯(lián)代碼或加載src中指定的文件。為了支持模塊,“模塊”值被添加??為類型選項(xiàng)。將類型設(shè)置為“module”會(huì)告訴瀏覽器將src指定的文件中包含的任何內(nèi)聯(lián)代碼或代碼作為模塊而不是腳本加載。

<!-- load a module JavaScript file -->
<script type="module" src="module.js"></script>

<!-- include a module inline -->
<script type="module">

import { sum } from "./example.js";

let result = sum(1, 2);

</script>

第一個(gè)加載外部的module.第二個(gè)<script>元素包含直接嵌入網(wǎng)頁的模塊。變量結(jié)果不會(huì)全局公開,因?yàn)樗鼉H存在于模塊中(由<script>元素定義),因此不會(huì)作為屬性添加到窗口中。

正如所見,包括網(wǎng)頁中的模塊相當(dāng)簡(jiǎn)單,類似于包含腳本。但是,模塊的加載方式存在一些差異。

可能已經(jīng)注意到“模塊”不是像“text / javascript”類型那樣的內(nèi)容類型。模塊JavaScript文件使用與腳本JavaScript文件相同的內(nèi)容類型提供,因此無法僅根據(jù)內(nèi)容類型進(jìn)行區(qū)分。此外,當(dāng)類型無法識(shí)別時(shí),瀏覽器會(huì)忽略<script>元素,因此不支持模塊的瀏覽器將自動(dòng)忽略<script type =“module”>行,從而提供良好的向后兼容性。

Module Loading Sequence in Web Browsers

模塊的獨(dú)特之處在于,與腳本不同,它們可以使用import來指定必須加載其他文件才能正確執(zhí)行。為了支持該功能,<script type =“module”>始終表現(xiàn)為應(yīng)用了defer屬性。defer屬性對(duì)于加載腳本文件是可選的,但始終應(yīng)用于加載模塊文件。 一旦HTML解析器遇到帶有src屬性的<script type =“module”>,模塊文件就會(huì)開始下載,但是在完全解析Document之后才會(huì)執(zhí)行。模塊也按它們?cè)?code>HTML文件中出現(xiàn)的順序執(zhí)行。這意味著第一個(gè)<script type =“module”>始終保證在第二個(gè)之前執(zhí)行,即使一個(gè)模塊包含內(nèi)聯(lián)代碼而不是指定src。
關(guān)于defer和async作用的script可以戳這里了解

<!-- this will execute first -->
<script type="module" src="module1.js"></script>

<!-- this will execute second -->
<script type="module">
import { sum } from "./example.js";

let result = sum(1, 2);
</script>

<!-- this will execute third -->
<script type="module" src="module2.js"></script>

每個(gè)模塊可以從一個(gè)或多個(gè)其他模塊導(dǎo)入,這使問題復(fù)雜化。 這就是為什么首先完全解析模塊以識(shí)別所有import語句的原因。 然后,每個(gè)import語句都會(huì)觸發(fā)一次獲取(來自網(wǎng)絡(luò)或來自緩存),并且在首次加載和執(zhí)行所有導(dǎo)入資源之前不會(huì)執(zhí)行任何模塊。

所有模塊,包括使用<script type =“module”>顯式包含的模塊和使用import隱式包含的模塊,都按順序加載和執(zhí)行。在前面的示例中,完整的加載順序是:

  1. 下載解析module1.js.
  2. 遞歸下載并解析module1.js中的導(dǎo)入資源。
  3. 解析內(nèi)聯(lián)模塊。
  4. 遞歸下載并解析內(nèi)聯(lián)模塊中的導(dǎo)入資源
  5. 下載解析module2.js
  6. 遞歸下載并解析module2.js中的導(dǎo)入資源。

加載完成后,在文檔完全解析之后才會(huì)執(zhí)行任何操作。文檔解析完成后,將執(zhí)行以下操作:

  1. 遞歸執(zhí)行module1.js的導(dǎo)入資源
  2. 執(zhí)行 module1.js
  3. 遞歸執(zhí)行內(nèi)聯(lián)模塊的導(dǎo)入資源
  4. 執(zhí)行內(nèi)聯(lián)模塊
  5. 遞歸執(zhí)行module2.js的導(dǎo)入資源
  6. 執(zhí)行module2.js

內(nèi)聯(lián)模塊的作用與其他兩個(gè)模塊類似,不是先下載代碼。加載導(dǎo)入資源和執(zhí)行模塊的順序完全相同。

<script type =“module”>上會(huì)忽略??defer屬性,因?yàn)樗男袨榫拖駪?yīng)用了defer一樣。

Asynchronous Module Loading in Web Browsers

與腳本一起使用時(shí),async會(huì)在下載和解析文件后立即執(zhí)行腳本文件。 但是,文檔中異步腳本的順序不會(huì)影響腳本的執(zhí)行順序。 腳本在完成下載后始終執(zhí)行,而不等待包含文檔完成解析。

async屬性也可以應(yīng)用于模塊。 在<script type =“module”>上使用async會(huì)導(dǎo)致模塊以類似于腳本的方式執(zhí)行。 唯一的區(qū)別是模塊的所有導(dǎo)入資源都是在執(zhí)行模塊之前下載的。 這保證了模塊執(zhí)行前所需的所有資源都將被下載; 但是你無法保證模塊何時(shí)執(zhí)行??聪旅娴拇a:

<!-- 不確定哪個(gè)先被執(zhí)行 -->
<script type="module" async src="module1.js"></script>
<script type="module" async src="module2.js"></script>
Loading Modules as Workers

Web WorkerService WorkerWorkerWeb頁面上下文之外執(zhí)行JavaScript代碼。 創(chuàng)建新worker需要?jiǎng)?chuàng)建一個(gè)新的實(shí)例Worker(或另一個(gè)類)并傳入JavaScript文件的位置。 默認(rèn)加載機(jī)制是將文件作為腳本加載,如下所示:

// load script.js as a script
let worker = new Worker("script.js");

為了支持加載模塊,HTML標(biāo)準(zhǔn)的開發(fā)人員為這些構(gòu)造函數(shù)添加了第二個(gè)參數(shù),第二個(gè)參數(shù)是一個(gè)具有type屬性的對(duì)象,其默認(rèn)值為“script”。您可以將類型設(shè)置為“module”以加載模塊文件:

// load module.js as a module
let worker = new Worker("module.js", { type: "module" });

module type worker一般類似于script type worker,但是有些例外。首先,script worker限制于同源,但是module worker不存在這些同源限制。 雖然module worker具有相同的默認(rèn)限制,但它們也可以加載具有適當(dāng)?shù)目缭促Y源共享(CORS)標(biāo)頭的文件以允許訪問。其次,雖然script worker可以使用self.importScripts方法將其他腳本加載到worker中,但self.importScripts總是在module worker上失敗,因?yàn)楦鼞?yīng)該使用import。

本文原文請(qǐng)戳這里

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容