ECMAScript 6 之用模組封裝程式碼

function0831發表於2018-02-18

JavaScript 用“共享一切”的方法載入程式碼,這是該語言中最容易出錯且最容易讓人感到困惑的地方。其他語言使用諸如包這樣的概念來定義程式碼作用域,但在 ECMAScript 6 以前,在應用程式的每一個 JavaScript 中定義的一切都共享一個全域性作用域。隨著 Web 應用程式變得更加複雜,JavaScript 程式碼的使用量也開始增長,這樣會引起問題,如命名衝突和安全問題。ECMAScript 6 的一個目標就是解決作用域問題,也為了使 JavaScript 應用程式顯得有序,於是引進了模組。

什麼是模組

模組是自動執行在嚴格模式下並且沒有辦法退出執行的 JavaScript 程式碼。與共享一切架構相反的是,在模組頂部建立的變數不會不會被自動新增到全域性共享作用域,這個變數僅在模組的頂級作用域中存在,而且模組必須匯出一些外部程式碼可以訪問的元素,如變數或函式。模組也可以從其他模組匯入繫結。

另外兩個模組的特性與作用域關係不大,但也很重要。首先,在模組的頂部,this 的值是 undefined ;其次,模組不支援 HTML 風格的程式碼註釋,這是從早期瀏覽器殘留下來的 JavaScript 特性。

指令碼,也就是任何不是模組的 JavaScript 程式碼,則缺少這些特性。模組和其他 JavaScript 程式碼之間的差異可能乍一看不起眼,但是它們代表了 JavaScript 程式碼載入和求值的一個重要變化。模組真正的魔力所在是僅匯出和匯入你需要的繫結,而不是將所有東西都到一個檔案。只有很好地理解了匯出和匯入才能理解模組與指令碼的區別。

匯出的基本語法

可以用 export 關鍵字將一部分已釋出的程式碼暴露給其他模組,在最簡單的用例中,可以將 export 放在任何變數、函式或類宣告的前面,以將它們從模組匯出,像這樣:

//匯出資料
export var color = "red";
export let name = "Nicholas";
export const magicNumber = 7;

//匯出函式
export function sum(num1,num2){
    return num1 + num2;
}

//匯出類
export class Rectangle {
    constructor(length,width){
        this.length = length;
        this.width = width;
    }
}

//這個函式是模組私有的
function subtract(num1,num2){
    return num1 - num2;
}

//定義一個函式...
function multiply(num1,num2){
     return num1 * num2;
} 

//...之後將它匯出
export multiply;
複製程式碼

在這個示例中需要注意幾個細節,除了 export 關鍵字外,每一個宣告與指令碼中的一模一樣。因為匯出的函式和類宣告需要有一個名稱,所以程式碼中的每一個函式或類也確實有這個名稱。除非用 default 關鍵字,否則不能用這個語法匯出匿名函式或類(隨後在“模組的預設值”中會詳細講解)。

另外,我們看 multiply() 函式,在定義它時沒有馬上匯出它。由於不必總是匯出宣告,可以匯出引用,因此這段程式碼可以執行。此外,請注意,這個示例並未匯出 subtract() 函式,任何未顯示匯出的變數、函式或類都是模組私有的,無法從模組外部訪問。

匯入的基本語法

從模組中匯出的功能可以通過 import 關鍵字在另一個模組中訪問,import 語句的兩個部分分別是: 要匯入的識別符號和識別符號應當從哪個模組匯入。

import { identifier1, identifier2 } from "./example.js";
複製程式碼

import 後面的大括號表示從給定模組匯入的繫結,關鍵字 from 表示從哪個模組匯入給定的模組,該模組由表示模組路徑的字串指定(被稱作模組說明符)。瀏覽器使用的路徑格式與傳統 <script> 元素的相同,也就是說,必須把副檔名也加上。另一方面,Node.js 則遵循基於檔案系統字首區分本地檔案和包的慣例。例如,example 是一個包而 ./example.js 是一個本地檔案。

備註: 匯入繫結的列表看起來與結構物件很相似,但它不是。

當從模組中匯入一個繫結時,它就好像使用 const 定義的一樣。結果是你無法定義一個同名變數(包括匯入另一個同名繫結),也無法在 import 語句前使用識別符號或改定繫結的值。

匯入單個繫結

假設前面的示例在一個名為 “example.js”的模組中,我們可以匯入並以多種方式使用這個模組中的繫結。舉例來說,可以只匯入一個識別符號:

//只匯入一個
import { sum } from "./example.js";

console.log(sum(1,2));       //3

sum = 1;                    //丟擲一個錯誤
複製程式碼

儘管 example.js 匯出的函式不止一個,但這個示例匯入的卻只有 sum() 函式。如果嘗試給 sum 賦新值,結果丟擲一個錯誤,因為不能給匯入的繫結重新賦值。

備註: 為了最好地相容多個瀏覽器和 Node.js 環境,一定要在字串之前包含 /./../來表示要匯入的檔案。

匯入多個繫結

如果你想從示例模組匯入多個繫結,則可以明確地將它們列出如下:

//匯入多個
import { sum, multiply, magicNumber } from "./example.js";
console.log(sum(1, magicNumber));      //8
console.log(multiply(1, 2));          //2
複製程式碼

在這段程式碼中,從 example 模組匯入3個繫結: sum、multiply和 magicNumber。之後使用它們,就像它們在本地定義一樣。

匯入整個模組

特殊情況下,可以匯入整個模組作為一個單一的物件。然後所有的匯出都可以作為物件的屬性使用。例如:

//匯入一切
import * as example from "./example.js";
console.log(example.sum(1, example.magicNumber));      //8
console.log(example.multiply(1, 2));          //2
複製程式碼

在這段程式碼中,從 example.js 中匯出的所有繫結被載入到一個被稱作 example 的物件中。指定的匯出(sum() 函式 mutiply() 函式和 magicNumber ) 之後會作為 example 的屬性被訪問。這種匯入格式被稱作名稱空間匯入(namespace import)。因為 example.js 檔案中不存在 example 物件,故而它作為 example.js 中所有匯出成員的名稱空間物件而被建立。

但是,請記住,不管在 import 語句中把一個模組寫了多少次,該模組將只執行一次。匯入模組的程式碼執行後,例項化過的模組被儲存在記憶體中,只要另一個 import 語句引用它就可以重複使用它。思考一下幾點:

import { sum } from "./example.js";
import { multiply } from "./example.js";
import { magicNumber } from "./example.js";
複製程式碼

儘管在這個模組中有3個 import 語句,但 example.js 將只執行一次。如果同一個應用程式中的其他模組也從 example.js 匯入繫結,那麼那些模組與此程式碼將使用相同的模組例項。

                                模組語法的限制
    export 和 import 的一個重要的限制是,它們必須在其它語句和函式之外使用。例如,
下面程式碼會給出一個語法錯誤:
if(flag){
    export flag;      //語法錯誤
}
    export 語句不允許出現在 if 語句中,不能有條件匯出或以任何方式動態匯出。模組
語法存在的一個原因是要讓 Javascript 引擎靜態地確定哪些可以匯出。因此只能在模組頂
部使用 export。
    同樣,不能在一條語句中使用 import,只能在頂部使用它。下面這段程式碼也會給出語法
錯誤:
function tryImport(){
    import flag from "./example.js";    //語法錯誤
}
    出於同樣的原因不能動態地匯入或匯出繫結。export 和 import 關鍵字被設計成靜態
的,因而像文字編輯器這樣的工具可以輕鬆地識別模組中那些資訊是可用的。
複製程式碼

匯入繫結的一個微妙怪異之處

ECMAScript 6import 語句為變數、函式和類建立的是隻讀繫結,而不是像正常變數一樣簡單地引用原始繫結。識別符號只有在被匯出的模組中可以修改,即便是匯入繫結的模組也無法更改繫結的值。例如,假設我們使用這個模組:

export var name = "Nicholas";
export function setName(newName){
    name = newName;
}
複製程式碼

當匯入這兩個繫結後,setName() 函式可以改變 name 的值:

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

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

name = "Nicholas";     //丟擲錯誤 
複製程式碼

呼叫 setName("Greg") 時會回到匯出 setName() 的模組中去執行,並將 name 設定為 "Greg"。請注意,此更改會自動在匯入的 name 繫結上體現。其原因是,name 是匯出的 name 識別符號的本地名稱。本段程式碼中所使用的 name 和模組中匯入的 name 不是同一個。

匯出和匯入時重名時

有時候,從一個模組匯入變數、函式或者類時,我們可能不希望使用它們的原始名稱。幸運的是,可以在匯出過程和匯入過程中改變匯出元素的名稱。

在第一種情況中,假設要使用不同的名稱匯出一個函式,則可以用 as 關鍵字來指定函式在模組外應該被稱為什麼名稱。

function sum(num1, num2){
    return num1 + num2;
}

export { sum as add };
複製程式碼

在這裡,函式 sum() 是本地名稱, add() 是匯出時使用的名稱。也就是說,當另一個模組要匯入這個函式時,必須使用 add 這個名稱:

import { add } from "./example.js";
複製程式碼

如果模組想使用不同的名稱來匯入函式,也可以使用 as 關鍵字:

import { add as sum } from "./example.js";
console.log(typeof add);           //"undefined"
console.log(sum(1, 2));           // 3
複製程式碼

這段程式碼匯入 add() 函式時使用了一個匯入名稱來重新命名 sum() 函式(當前上下文中的本地名稱)。匯入時改變函式的本地名稱意味著即使模組匯入了 add() 函式,在當前模組中也沒有 add() 識別符號。

模組的預設值

由於在諸如 CommonJS ( 瀏覽器外的另一個 JavaScript 使用規範 )的其他模組系統中,從模組中匯出和匯入預設值是一個常見的做法,該語法被進行了優化。模組的預設值指的是通過 default 關鍵字指定的單個變數、函式或類,只能為每個模組設定一個預設的匯出值,匯出時多次使用 default 關鍵字是一個語法錯誤。

匯出預設值

下面是一個使用 default 關鍵字的簡單示例:

export default function(num1, num2){
    return num1 + num2; 
}
複製程式碼

這個模組匯出了一個函式作為它的預設值,default 關鍵字表示這是一個預設的匯出,由於函式被模組所代表,因而它不需要一個名稱。

也可以在 export default 之後新增預設匯出值的識別符號,就像這樣:

function sum(num1, num2){
    return num1 + num2; 
}
export default sum;
複製程式碼

先定義 sum() 函式,然後再將其匯出為預設值,如果需要計算預設值,則可以使用這個方法。

為預設匯出值指定識別符號的第三種方法是使用重新命名方法,如下所示:

function sum(num1, num2){
    return num1 + num2; 
}
export { sum as default };
複製程式碼

在重新命名匯出時識別符號 default 具有特殊含義,用來指定模組的預設值。由於 defaultJavaScript 中的預設關鍵字,因此不能將其用於變數、函式或類的名稱;但是,可以將其用作屬性名稱。所以用 default 來重新命名模組是為了儘可能與非預設匯出的定義一致。如果想在一條匯出語句中同時指定多個匯出(包括預設匯出),這個語法非常有用。

匯入預設值

可以使用以下語法從一個模組匯入一個預設值:

//匯入預設值
import sum from "./example.js";

console.log(sum(1, 2));      //3
複製程式碼

這條 import 語句從模組 example.js 中匯入了預設值,請注意,這裡沒有使用大括號,與你見過的非預設匯入的情況不同。本地名稱 sum 用於表示模組匯出的任何預設函式,這種語法是最純淨的,ECMAScript 6 的建立者希望它能夠成為 web 上主流的模組匯入形式,並且可以使用已有的物件。

對於匯出預設值和一或多個非預設繫結的模組,可以用一條語句匯入所有匯出的繫結。例如,假設有以下這個模組:

export let color = "red";

export default function(num1, num2){
    return num1 + num2;
}
複製程式碼

可以用以下這條 import 語句匯入 color 和預設函式:

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

console.log(sum(1, 2));     //3
console.log(color);        //"red"
複製程式碼

用逗號將預設的本地名稱與大括號包裹的非預設值分隔開,請記住,在 import 語句中,預設值必須排在非預設值之前。

與匯出預設值一樣,也可以在匯入預設值是使用重新命名語法:

import { default as sum, color } from "./example.js";

console.log(sum(1, 2));     //3
console.log(color);        //"red"
複製程式碼

在這段程式碼中,預設匯出(export)值被重新命名為 sum,並且還匯入了 color。該示例與之前的示例相同。

重新匯出一個繫結

最終,可能需要重新匯出模組已經匯入的內容。例如,你正在用幾個小模組建立一個庫,則可以用已經討論的模式重新匯出已經匯入的值,如下所示:

import { sum } from "./example.js";
export { sum }
複製程式碼

雖然這樣可以執行,但只通過一條語句也可以完成同樣的任務:

export { sum } from "./example.js";
複製程式碼

這種形式的 export 在指定的模組中查詢 sum 宣告,然後將其匯出。當然對於同樣的值你也可以不同的名稱匯出:

export { sum as add } from "./example.js";
複製程式碼

這裡的 sum 是從 example.js 匯入的,然後再用 add 這個名字將其匯出。如果想匯出另一個模組中的所有值,則可以使用*模式:

export * from "./example.js";
複製程式碼

匯出一切是指匯出預設值及所有命名匯出值,這可能會影響你可以從模組匯出的內容。例如,如果 example.js 有預設的匯出值,則使用此語法時將無法定義一個新的預設匯出。

無繫結匯入

某些模組可能不匯出任何東西,相反,它們可能只修改全域性作用域中的物件。儘管模組中的頂層變數、函式和類不會自動地出現在全域性作用域中,但這並不意味著模組無法訪問全域性作用域。內建物件(如Array和Object)的共享定義可以在模組中訪問,對這些物件所做的更改將反映在其他模組中。

例如,要向所有陣列新增 pushAll() 方法,則可以定義如下所示的模組:

//沒有export或import的模組程式碼
Array.prototype.pushAll = function(items){
    
    //items必須是一個陣列
    if (!Array.isArray(items)){
        throw new TypeError("引數必須是一個陣列。")
    }
    
    //使用內建的push()和展開運算子
    return this.push(...items);
};
複製程式碼

即使沒有任何匯出和匯入的操作,這也是一個有效的模組。這段程式碼即可用作模組也可以用作指令碼。由於它不匯出任何東西,因而你可以使用簡化的匯入操作來執行模組程式碼。而且不匯入任何的繫結:

import "./example.js";

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

items.pushAll(colors);
複製程式碼

這段程式碼匯入並執行了模組中包含的 pushAll() 方法,所以 pushAll() 被新增到陣列的原型,也就是說現在模組中的所有陣列都可以使用 pushAll() 方法了。

備註:無繫結匯入最有可能被應用於建立 PolyfillShim

載入模組

雖然 ECMAScript 6 定義了模組的語法,但它並沒有定義如何載入這些模組。這正是規範複雜性的一個體現,應有不同的實現環境來決定。ECMAScript 6 嘗試為所有 JavaScript 環境建立一套統一的標準,它只規定了語法,並將載入機制抽象到一個未定義的內部方法 HostResolveImportedModule 中。Web 瀏覽器和 Node.js 開發者可以通過對各自環境的認知來決定如何實現 HostResolveImportedModule

在Web瀏覽器中使用模組

即使在 ECMAScript 6 出現以前,Web 瀏覽器有多種方式可以將 JavaScript 包含在Web 應用程式中,這些指令碼載入的方法分別是:

  • <script> 元素中通過 src 屬性指定一個載入程式碼的地址來載入 JavaScript 程式碼檔案。
  • JavaScript 程式碼內嵌到沒有 src 屬性的 script 元素中。
  • 通過 Web WorkerService Worker 的方法載入並執行 JavaScript 程式碼檔案。

為了完全支援模組功能,Web 瀏覽器必須更新著這些機制,下面我們來總結一下。

<script> 中使用模組

<script> 元素的預設行為是將 JavaScript 檔案作為指令碼載入,而非作為模組載入,當 type 屬性缺失或包含一個 JavaScript 內容型別(如"text/javascript")時就會發生這種情況。script 元素可以執行內聯程式碼或載入 src 中指定的檔案,當 type 屬性的值為“module”時支援載入模組。將 type 設定為“module”可以讓瀏覽器將所有內聯程式碼或包含在 src 指定的檔案中的程式碼按照模組而非指令碼的方式載入。這裡有個簡單的示例:

<!--載入一個 JavaScript 模組檔案 -->
<script type="module" src="module.js"></script>

<!--內聯引入一個模組 --> 
<script type="module">

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

let result = sum(1, 2);

</script>
複製程式碼

此示例中的第一個 script 元素使用 src屬性載入了一個外部的模組檔案,它與載入指令碼之間的唯一區別是 type 的值是“module”。第二個 script 元素包含了直接嵌入在網頁的模組。變數 result 沒有暴露到全域性作用域,它只存在於模組中(由 <script> 元素定義 ),因此不會被新增到 window 作為它的屬性。

如你所見,在 web 頁面中引入模組的過程類似於引入指令碼,相當簡單。但是,模組yu實際的載入過程卻有一些不同。

注意:你可能已經注意到,“module”與“text/javascript” 這樣的內容型別並不相同。JavaScript 模組檔案與 JavaScript 指令碼檔案就有相同的內容型別,因此無法僅根據內容型別進行區分。此外,當無法識別 type 的值時,瀏覽器會忽略 <script> 元素,因此不支援模組的瀏覽器將自動忽略 <script type="module"> 來提供良好的向後相容性。

Web 瀏覽器中的模組載入順序

模組與指令碼不同,它是獨一無二的,可以通過 import 關鍵字來指明其所依賴的其他檔案,並且這些檔案必須被載入進該模組才能正確執行。為了支援該功能,<script type="module">執行時自動應用 defer 屬性。 記載指令碼檔案時,defer 是可選屬性;載入模組時,它就是必需屬性。一旦 HTML 解析器遇到具有 src 屬性的 <script type="module">,模組檔案便開始下載,直到文件被完全解析模組才會執行。模組按照它們出現在 HTML 檔案中的順序執行,也就是說,無論模組中包含的是內聯程式碼還是指定 src 屬性,第一個 <script type="module"> 總是在第二個之前執行。例如:

<!-- 先執行這個標籤 -->
<script type="module" src="module1.js"></script>

<!-- 再執行這個標籤 --> 
<script type="module">

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

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

<!-- 最後執行這個標籤 -->
<script type="module" src="module2.js"></script>
複製程式碼

這3個 script 元素按照它們被指定的順序執行,所以模組 module1.js 保證會在內聯模組前執行,而內聯模組保證會在 module2.js 模組之前執行。

每個模組都可以從一個或多個其他的模組匯入,這會使問題複雜化。因此,首先解析模組以識別所有匯入語句;然後,每個匯入語句都觸發一次獲取過程(從網路或從快取),並且在所有匯入資源都被載入和執行後才會執行當前模組。

<script type="module"> 顯示引入和用 import 隱式匯入的所有模組都是按需載入並執行的。在這個示例中,完整的載入順序如下:

1.載入並解析 module1.js。
2.遞迴下載並解析 module1.js中匯入的資源。
3.解析內聯模組。
4.遞迴下載並解析內聯模組中匯入的資源。
5.載入並解析 module2.js。
6.遞迴下載並解析 module2.js中匯入的資源。
複製程式碼

載入完成後,只有當文件完全被解析之後才會執行其他操作。文件解析完成後,會發生以下操作:

1.遞迴執行 module1.js 中匯入的資源。
2.執行 module1.js。
3.遞迴執行內聯模組中匯入的資源。
4.執行內聯模組。
5.遞迴執行 module2.js中匯入的資源。
6.執行 module2.js。
複製程式碼

請注意,內聯模組與其他兩個模組唯一的不同是,它不必先下載模組程式碼。否則,載入匯入資源和執行模組的順序就是一樣的。

備註: <script type="module"> 元素會忽略 defer 屬性預設是存在的。

Web 瀏覽器中的非同步模組載入

你可能熟悉 <script> 元素上的 async 屬性。當其應用於指令碼時,指令碼檔案將在檔案完全載入並解析後執行。但是,文件中 async 指令碼的順序不會影響指令碼執行的順序,指令碼在下載完成後立即執行,而不必等待包含的文件完成解析。

async 屬性也可以應用在模組上,在 <script type="module"> 元素上應用 async 屬性會讓模組以類似指令碼的方式執行,唯一的區別是,在模組執行前,模組中所有的匯入資源都必須下載下來。這可以確保只有當模組執行所需的所有資源都下載完成後才執行模組,但不能保證的是模組的執行時機。請考慮以下程式碼:

<!-- 無法保證這兩個哪個先執行 -->
<script type="module" async src="module1.js"></script>
<script type="module" async src="module2.js"></script>
複製程式碼

在這個示例中,兩個模組檔案被非同步載入。只是簡單地看這個程式碼判斷不出哪個模組先執行,如果 module1.js 首先完成下載(包括所有的匯入資源),它將先執行;如果 module2.js 首先完成下載,那麼它將先執行。

將模組作為 Worker 載入

Worker, 例如 Web WorkerService Worker,可以在網頁上下文之外執行 JavaScript 程式碼。建立新 Worker 的步驟包括:建立一個新的 Worker 例項(或其他的類),傳入 JavaScript 檔案的地址。預設的載入機制是按照指令碼的方式載入檔案,如下所示:

//按照指令碼的方式載入script.js
let worker = new Worker("script.js");
複製程式碼

為了支援載入模組,HTML 標準的開發者向這些建構函式新增了第二個引數,第二個引數是一個物件,其 type 屬性的預設值為“script”。可以將 type 設定為“module”來載入模組檔案:

//按照模組的方式載入module.js
let worker = new Worker("module.js",{ type:"module" });
複製程式碼

在此示例中,給第二個引數傳入一個物件,其 type 屬性的值為“module”,即按照模組而不是指令碼的方式載入 module.js。(這裡的 type 屬性是為了模仿 <script> 標籤的 type 屬性,用以區分模組和指令碼。)所有瀏覽器中的 Worker 型別都支援第二個引數。

Worker 模組通常與 Worker 指令碼一起使用,但也有一些例外。首先,Worker 指令碼只能從與引用的網頁相同的源載入,但 Worker 模組不會完全受限,雖然 Worker 模組具有相同的預設限制,但它們還是可以將載入並訪問具有適當的跨域資源共享(CORS)頭的檔案;其次儘管 Worker 指令碼可以使用 self.importScripts() 方法將其他指令碼載入到 Worker 中, 但 self.importScripts() 卻始終無法載入 Worker 模組,因為應該使用 import 來匯入。

瀏覽器模組說明符解析

之前的所有示例,模組說明符(module specifier)使用的都是相對路徑(例如,字串“./example.js”),瀏覽器要求模組說明符具有以下幾種格式之一:

  • / 開頭的解析為從根目錄開始。
  • ./ 開頭的解析為從當前目錄開始。
  • ../ 開頭的解析為從父目錄開始。
  • URL格式。

例如,假設有一個模組檔案為於https://www.example.com/modules/module.js,其中包含以下程式碼:

//從 https://www.example.com/modules/example1.js匯入
import { first } from "./example1.js";

//從 https://www.example.com/example2.js匯入
import { second } from "../example2.js";

//從 https://www.example.com/example3.js匯入
import { third } from "/example3.js";

//從 https://www2.example.com/example4.js匯入
import { fourth } from "https://www2.example.com/example4.js";
複製程式碼

此示例中的每個模組說明符都適用於瀏覽器,包括最後一行中完整的 URL。(為了支援跨域載入,只需確保 www2.example.comCORS 頭的配置是正確的。)儘管尚未完成的模組載入器規範將提供解析其他格式的方法,但目前,這些是瀏覽器預設情況下唯一可以解析的模組說明符的格式。

故此,一些看起來正常的模組說明符在瀏覽器中實際上是無效的,並且會導致錯誤,例如:

//無效的,沒有以/,./或../開頭
import { first } from "example.js";

//無效的,沒有以/,./或../開頭
import { second } from "example/index.js";
複製程式碼

由於這兩個模組說明符的格式不正確(缺少正確的起始字元),因此它們無法被瀏覽器記載,即使在 <script> 標籤中用作 src 的值時二者都可以正常工作。 <script>import 之間的這種行為差異是有意為之。

總結

ECMAScript 6 語言中的模組是一種打包和封裝功能的方式,模組的行為與指令碼不同,模組不會將它的頂級變數、函式和類修改為全域性作用域,而且 this 的值為 undefined。要實現這個行為,需通過不同的模式來載入模組。 必須匯出所有要讓模組使用者使用的功能,變數、函式和類都可以匯出,每個模組還可以有一個預設的匯出值。匯出後,另一個模組可以匯入部分或所有匯出的名稱,這些名稱表現得像是通過 let 定義的,執行起來與塊級作用域繫結一樣,在同一個模組中無法重新宣告它們。

如果模組只操作全域性作用域,則不需要匯出任何值。實際上,匯入這樣一個模組不會將任何繫結引入到當前的模組作用域。

由於模組必須執行在不同的模式下,因此瀏覽器引入 <script type="module"> 來表示模組中應該執行的原始檔或內聯程式碼。通過 <script type="module"> 載入的模組檔案預設具有 defer 屬性。在文件完全被解析之後,模組也按照它們在包含文件中出現的順序依次執行。

最後祝願所有的前端攻城獅新年快樂,願大家狗年旺旺,前途似錦,單身狗在狗年告別單身,歡迎大家加入前端全棧技術交流群544587175,相互交流,共同進步。

相關文章