## 發展概述 > ![image](https://hackmd.io/_uploads/HJZB6tgV6.png) > - > 網景領航員 (Netscape Navigator 2):第一個支援 JavaScript 以及 GIFs 的瀏覽器 ([source](https://en.wikipedia.org/wiki/Netscape_Navigator_2)) > JavaScript 早期為輕量的腳本式語言,僅用於網頁中的簡單使用者互動。 隨著網際網路發展,程式碼的規模也跟著需求逐漸成長,然而由於原生 JavaScript 並未支援模組系統,社群於 2009 年發展出兩套主流的模組系統: * `CommonJS Modules`:主要用於伺服器端(例如`Node.js`) * `Asynchronous Module Definition` (AMD):主要用於瀏覽器端,支援非同步載入模組。`RequireJS`為以 AMD 為基礎的模組系統 最終,JavaScript 在 2015 年的 ES6 中加入模組化概念、規範及語法,以解決專案的組織、管理與維護需求。除了程式碼重複使用、避免命名衝突等優點,瀏覽器端也能最佳化載入模組,相較於一次性載入整個函式庫,更加有效率。 ## 使用要點 1. 載入 ES6 模組時必須將`script`標籤的`type`屬性的值設為`module` ```html <script type="module" src="./module.js"></script> ``` 2. 無論是否使用`use strict`語法,都會自動採用嚴格模式 ([strict mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode)) :::info 嚴格模式的特點:消除語法和程式碼的不安全處、提高性能、改進錯誤處理、禁止使用某些不推薦的特性 ::: 3. 每個檔案視為一個獨立的模組,模組中所有名稱(如變數、常數、函式、物件、類別等)的**作用域**僅限檔案內部,其他檔案無法取用。若要提供給外部使用,則需利用`export`、`import`進行匯入及匯出 4. 使用`export`關鍵字匯出,通常置於**最後方**。使用`import`關鍵字匯入,通常置於**最前方** ## 語法概要 ### 多個輸出名稱 #### `Named Export`:具名匯出 * 每個模組中可使用多個`export`直接匯出 ```javascript // module.js export let num = 1; export const sayHello = "Hello!"; export function greeting () { console.log (sayHello) }; export const user = { name: "John" }; ``` * 除了直接匯出的方法外,也可將要輸出的名稱放入`{}`中 ```javascript // module.js let num = 1; const sayHello = "Hello!"; function greeting () { console.log (sayHello) }; const user = { name: "John"}; export { num, sayHello, greeting, user }; // 將所有要輸出的名稱放入{}中 ``` * 匯出的名稱可利用`as`關鍵字重新命名 ```javascript // module.js const user = { name: "John"}; export { user as userName }; // 重新命名 ``` * 由於`export`的目標為名稱,而不是值,因此若重新賦值,匯入後取得的會是最新的值 ```javascript let num = 1; export { num }; num = 2; // 匯入此模組後取得num的值會是2 ``` #### `Import` * 匯入的名稱同樣可利用`as`關鍵字重新命名 ```javascript import { user as userName } from "./module.js"; ``` * 利用萬用字元`*`一次匯入模組中所有的輸出內容,此時輸出內容被放在一個自訂名稱的物件中,此方法屬於命名空間載入`namespace import` ```javascript // 將整個模組的內容匯入到名為module的物件中 import * as module from "./module.js"; // 以物件的方式取出值 console.log (module.sayHello); // "Hello!" ``` #### 不合法的使用 :::warning **注意:以`import`語法匯入的名稱無法重新賦值,且`import`及`export`必須放在模組頂層,無法放在區塊或函式中([靜態分析](https://en.wikipedia.org/wiki/Static_program_analysis)時不執行程式碼)** ::: * 不可重新賦值 ```javascript // module.js export let num = 1; // main.js import { num } from "./module.js"; num = 2; // 此時會報錯 ``` * 不可放在模組頂層外 ```javascript if (true) { import { num } from "./module.js"; // 不可在if中使用import } function numX() { export const x = 2; // 不可在函式中使用export } ``` ### 單一輸出名稱 #### `Default Export`:預設匯出 :::danger **注意:每個模組內只能有一個`export default`** ::: * 當準備匯出成模組的檔案中,只存在一個要輸出的目標,通常會使用`default`關鍵字 ```javascript // multiply.js function multiplyFn (num) { return num * num }; export default multiplyFn; ``` 匯入單一輸出的模組時**不需使用`{}`**,因為此時只匯入以`default`定義的輸出目標 ```javascript import multiplyFn from "./multiply.js"; multiplyFn(3); ``` 也可在匯入時更改名稱,避免命名衝突 ```javascript import square from "./multiply.js"; // 將原本的multiplyFn改為square ``` * 使用`default`關鍵字時,也可不定義名稱、直接匯出 ```javascript // module.js const sayHello = "Hello!"; // 匯出匿名函式 export default function () { console.log (sayHello) }; ``` ```javascript import greeting from "./module.js"; greeting(); // "Hello!" ``` * 要特別注意的是`default export`與`named export`不同,即使再對已進行預設匯出的名稱重新賦值,匯入後取得的並不會是最新的值 ```javascript let num = 1; export default num; num = 2; // 匯入此模組後取得num的值仍然是1 ``` #### 同時使用`export`、`export default`? `export`、`export default`可同時存在,但不建議這樣使用 ```javascript // module.js export const sayHello = "Hello!"; export default "Hi!"; ``` ```javascript import { sayHello } from "./module.js"; import sayHi from "./module.js"; // 合併匯入時,default需放在前 import sayHi, { sayHello } from "./module.js"; ``` ## 總結 ### 各式寫法 #### `named export`:顧名思義,匯出時皆需帶有名稱 ```javascript // module.js // 1 export const num = 1; // 2 const num = 1; export { num }; // 3 const num = 1; export { num as number }; ``` #### `import` ```javascript // 1 import { num } from "./module.js"; // 2 import { num as number } from "./module.js"; // 3: 將模組所有輸出項目放入module物件中 import * as module from "./module.js"; ``` #### `default export` ```javascript // module.js // 1: 可匯出純值 export default 1; // 2 const num = 1; export default num; ``` 由於一個模組中只有一個`default export`,匯入時不需要加上`{}` ```javascript // 1: 使用原始名稱 import num from "./module.js" // 2: 自訂名稱 import number from "./module.js" ``` ### 合法的語法 #### `export` ```javascript export let num = 1; // export a names variable export function sum () {}; // export a named function export default 1; // export the default export export default function sum () {}; // export the default export as a function export { greeting }; // export an existing variable export { hello as eng }; // export a variable as a new name export { hola as esp } from "module"; // export an export from another module export * from "module"; // export all exports from another module ``` #### `import` ```javascript import "module"; // import a module without any import bindings import num from "module"; // import the default export of a module import { num } from "module"; // import a named export of a module import { num as newNum } from "module"; // import a names export to a fidderent name import * as allModule from "module"; // import an entire module instance object ``` ## 參考文章 * [JavaScript modules | MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) * [模組系統·從ES6開始的JavaScript學習生活](https://eyesofkids.gitbooks.io/javascript-start-from-es6/content/part4/module_system.html) * [ES6 Module](https://chupai.github.io/posts/2104/es6module/) * [[JS] JavaScript 模組(ES Module, ESM)](https://pjchender.dev/javascript/js-es-module/) * [JavaScript :: 模組入門](https://openhome.cc/zh-tw/javascript/class-module/module/) * [模組化 (1) - Export / Import](https://medium.com/@jedy05097952/模組化-1-es6-export-import-2df769cbd81b)