ECMAScript 之 Module
栏目: JavaScript · 发布时间: 6年前
内容简介:ES5 很難寫大程式,主要是因為 JavaScript 沒有 Module 概念,常常一個檔案寫兩三千行程式,且大量使用 Global Variable 造成 Side Effect 很難維護。早期 JavaScript 是使用 Module Pattern 解決,稍後更有 CommonJS 與 AMD 試圖制定 Module 標準,一直到 TC39 出手,在 ECMAScript 2015 定義 Module 後,JavaScript 的模組化總算塵埃落定,是 JavaScript 發展的重要里程碑。E
ES5 很難寫大程式,主要是因為 JavaScript 沒有 Module 概念,常常一個檔案寫兩三千行程式,且大量使用 Global Variable 造成 Side Effect 很難維護。
早期 JavaScript 是使用 Module Pattern 解決,稍後更有 CommonJS 與 AMD 試圖制定 Module 標準,一直到 TC39 出手,在 ECMAScript 2015 定義 Module 後,JavaScript 的模組化總算塵埃落定,是 JavaScript 發展的重要里程碑。
Version
ECMAScript 2015
Why Module ?
在 ES5 時代,Scope 只有兩種概念:Global 與 Function,而沒有如 C# 的 Namespace 或 Java 的 Package,因此很難將程式碼加以模組化,造成 JavaScript 很難寫大程式。
Module 須提供兩大功能:
- 將 data 或 function 封裝在 Module 內
- 將 interface 暴露在 Module 外
ES5 在語言層級並沒有提供以上支援。
Module Pattern
MouseCounterModule.js
const MouseCounterModule = function() { let numClicks = 0; const handleClick = () => console.log(++numClicks); return { countClick: () => document.addEventListener('click', handleClick); }; }();
當語言不支援時,第一個會想用的就是 Design Pattern 自救。
JavaScript 什麼都是用 function,Module 也用 function 就不意外了。
- 將 data 或 function 封裝在 Module 內 :使用了 Closure + IIFE
- 將 interface 暴露在 Module 外 :return 全新 object
IIFE
Immediately Invoked Function Expression
定義 function 的同時,也順便執行 function,若配合 Closure,可將 data 封裝在 function 內,避免 data 暴露在 Global Scope
main.js
<script type="text/javascript" src="MouseCounterModule.js"/> <script type="texty/javascript"> MouseCounterModule.counterClick(); </script>
使用 HTML 載入 Dependency Module,此時 JavaScript 的載入順序就很重要,需要自行控制。
AMD
AMD
Asynchronous Module Defintion
針對 Browser 所設計的 Module 解決方案,使用 Asynchronous 方式載入 Module
MouseCounterModule.js
define('MouseCounterModule', ['jQuery'], $ => { let numClicks = 0; const handleClick = () => console.log(++numClicks); return { countClick: () => $(document).on('click', handleClick); }; });
define()
為 AMD 所提供的 function:
- 第一個參數:定義 Module 的 ID 作為識別
- 第二個參數:陣列,傳入其他 Dependency Module 的 ID
- 第三個參數:用來建立 Module 的 function
除了 define()
外,該寫的 Module function 還是要寫。
- 將 data 或 function 封裝在 Module 內 :在 function 內使用 Closure 封裝
- 將 interface 暴露在 Module 外 :return 全新 object
不必再使用 IIFE, define()
會幫你執行。
main.js
require(['MouseCounterModule'], mouseCounterModule => mouseCounterModule.countClick(); );
require()
為 AMD 所提供的 function:
- 第一個參數:相依的外部 Module ID
- 第二個參數:使用 Module 的 function
AMD 有以下特色:
define()
也因為 AMD 的 asynchronous 特性,特別適合在 Browser 使用。
CommonJS
CommonJS
為一般性 JavaScript 環境所設計的解決方案,Node.js 使用
MouseCounterModule.js
const $ = require('jQuery'); let numClicks = 0; const handleClick = () => console.log(++numClicks); module.exports = { countClick: () => $(document).on('click', handleClick); };
require()
為 CommonJS 所提供的 function,負責載入 Dependency Module。
-
將 data 或 function 封裝在 Module 內:
numClicks
與handleClick()
看似 Global,但事實上其 scope 只有 Module level,不用特別使用 function 與 Closure 寫法就能達成封裝 data 與 function。 -
將 interface 暴露在 Module 外:將全新 object 指定給
module.exports
即可,不需特別 return
main.js
const MouseCounterModule = require('MouseCounterModule.js'); MouseCounterModule.counterClick();
使用 require()
載入 Dependency Module 後即可使用,也不用搭配 Callback function。
CommonJS 有以下特色:
- Data 與 function 不需再使用 Closure,雖然看起來像 Global,但 CommonJS 會封裝在 Module 內
- 使用 module.exports 公開 interface
- 一個檔案就是一個 Module
- 語法比 AMD 優雅
但 CommonJS 也有幾個缺點:
-
但
require()
為 Synchronous,因此適合在 server 端使用 -
Browser 並未提供
module
與exports
,因此還要透過Browserify
作轉換
ES Module
由於 JavaScript 社群存在這兩大 Module 標準,TC39 決定融合 AMD 與 CommonJS 的優點制定出 ES6 Module,至此 JavaScript 有了正式的 Module 規格
- 學習 CommonJS,一個檔案就是一個 Module
- 學習 CommonJS 簡單優雅的語法
- 學習 AMD 以 Asynchronous 載入 Module
MouseCounterModule.js
import $ from 'jquery'; let numClicks = 0; const handleClick = () => console.log(++numClicks); export default { countClick: () => $(document).on('click', handleClick); };
import
為 ECMAScript 2015 所提供的 keyword,負責載入 Dependency Module,可以 Synchronous 也可 Asynchronous。
export
為 ECMAScript 2015 所提供的 keyword,負責暴露 interface 於 Module 外。
-
將 data 或 function 封裝在 Module 內:
numClicks
與handleClick()
看似 Global,但事實上其 scope 只有 Module level,不用特別使用 function 與 Closure 寫法就能達成封裝 data 與 function,這點與 CommonJS 一樣 -
將 interface 暴露在 Module 外:將全新 object 透過
export
即可,不需特別 return
main.js
import MouseCounterModule from 'MouseCounterModule.js'; MouseCounterModule.counterClick();
使用 import
載入 Dependency Module 後即可使用,也不用搭配 Callback function,這點與 CommonJS 一樣。
CommonJS 有以下特色:
-
提供
export
與import
兩個 keyword 就解決 - 語法比 CommonJS 優雅
Definition
你可以將 data (variable、object、function、class) 加以 imprt 與 export。
Export 分為 Named Export 與 Default Export:
- Named Export :data 必須有名稱
- Default Export :data 沒有名稱 (Anonymous Object、Anonymous Function、Anonymous Class)
- 一個 Module 只能有一個 Default Export,但能有無限多個 Named Export
Default Export 的 data 也可以有名稱,但因為回由 import 決定名稱,所以通常會使 data 沒有名稱
Named Export
Variable
my-module.js
export let x = 2; export const y = 3;
可直接對 let
與 const
變數加以 export 。
main.js
import { x } from 'my-module' import { y } from 'my-module' console.log(x); console.log(y); // 2 // 3
可對變數分別 import,但 Named Import 要搭配 {}
。
Object
my-module.js
let x = 2; const y = 3; export { x, y };
可直接對 object 加以 export。
main.js
import { x, y } from 'my-module' console.log(x); console.log(y); // 2 // 3
可對 object 直接 import,使用 Object Destructing 方式對 object 直接解構。
Function
my-module.js
export function add(x, y) { return x + y; }
將 add()
加以 export。
main.js
import { add } from 'my-module' console.log(add(1, 1)); // 2
對 function 加以 Named Import 要加上 {}
。
my-module.js
export const add = (x, y) => x + y;
將 add()
Arrow Function 加以 export。
main.js
import { add } from 'my-module' console.log(add(1, 1)); // 2
對 Arrow Function 加以 Named Import 也要加上 {}
。
Class
my-module.js
export class Counter { constructor(x, y) { this.x = x; this.y = y; } sum() { return this.x + this.y; } }
將 Counter
class 加以 export。
main.js
import { Counter } from 'my-module' const counter = new Counter(1, 1); console.log(counter.sum()); // 2
對 class 加以 Named Import 要加上 {}
。
無論對 variable / object / function / class 加以 Named Export,都會事先明確命名,然後在 Named Import 時都加上 {}
Default Export
Variable
ES 6 無法對 var
、 let
與 const
使用 Default Export。
Object
my-module.js
const x = 2; const y = 3; export default { x, y };
對於 Anonymous Object 可使用 Default Export。
main.js
import MyObject from 'my-module' console.log(MyObject.x); console.log(MyObject.y);
對 Anonymous Object 使用 Default Import,由於 Anonymous Object 本來就沒有名字,要在 Default Import 重新命名。
Default Import 不用加上 {}
。
Function
my-module.js
export default function(x, y) { return x + y; }
對於 Anonymous Function 可使用 Default Export。
main.js
import add from 'my-module' console.log(add(1, 1));
對 Anonymous Function 使用 Default Import,由於 Anonymous Function 本來就沒有名字,要在 Default Import 重新命名。
Default Import 不用加上 {}
。
my-module.js
export default (x, y) => x + y;
對於 Arrow Function 可使用 Default Export。
main.js
import add from 'my-module' console.log(add(1, 1));
對 Arrow Function 使用 Default Import,由於 Arrow Function 本來就沒有名字,要在 Default Import 重新命名。
Class
my-module.js
export default class { constructor(x, y) { this.x = x; this.y = y; } sum() { return this.x + this.y; } }
對於 Anonymous Class 可使用 Default Export。
main.js
import Counter from 'my-module' const counter = new Counter(1, 1); console.log(counter.sum());
對 Anonymous Class 使用 Default Import,由於 Anonymous Class 本來就沒有名字,要在 Default Import 重新命名。
無論對 variable / object / function / class 加以 Default Export,可不用事先明確命名 (當然要事先命名亦可,但沒有太大意義),然後在 Named Import 時不用加上 {}
React 與 Vue 喜歡使用 Default Export,優點是可由 user 自行命名,彈性最高;Angular 則喜歡使用 Named Export,由 Framework 事先命名,優點是整個 community 名稱統一
Named + Default Export
一個 Module 只允許一個 Default Export,但可以有多個 Named Export。
my-module.js
export const name = 'Sam'; export const add = (x, y) => x + y; export default class { constructor(x, y) { this.x = x; this.y = y; } sum() { return this.x + this.y; } }
name
與 add()
為 Named Export,但 Anonymous Class 為 Default Export。
main.js
import { name } from 'my-module' import { add } from 'my-module' import Counter from 'my-module' console.log(name); console.log(add(1, 1)); const counter = new Counter(1, 1); console.log(counter.sum()); // Sam // 2 // 2
Named Export 要搭配 Named Import。
Default Export 則搭配 Default Export。
Import Entire Module
實務上一個 Module 可能有很多 Export,要一個一個 Import 很辛苦,可以將整個 Module 都 Import 進來。
main.js
import * as MyModule from 'my-module' console.log(MyModule.name); console.log(MyModule.add(1, 1)); const counter = new MyModule.default(1, 1); console.log(counter.sum());
對於 Named Export 沒問題,名字會維持原來的名稱。
但對於沒有名稱的 Default Export,會以 default
為名稱。
Alias
若對原本 data 名稱覺得不滿意,在 Named Export 或 Named Import 時都可以重新取別名。
my-module.js
const add = (x, y) => x + y; export { add as sum };
在 Named Export 時,已經使用 as
將 add
取別名為 sum
,需搭配 {}
。
main.js
import { sum } from 'my-module' console.log(sum(1, 1));
既然已經取別名為 sum
,就以 sum
為名稱 import 進來。
my-module.js
export const add = (x, y) => x + y;
直接使用 Named Export 將 add()
export 出來。
main.js
import { add as sum } from 'my-module' console.log(sum(1, 1));
在 Named Import 時才使用 as
取別名亦可。
Conclusion
-
ES6 Module 語法很簡單,只有
export
與import
兩個 keyword - ES6 Module 分成 Named Export 與 Default Export,一個 Module 只能有一個 Default Export,但可以有多個 Named Export
-
可以使用
import * as module
,將整個 Module 都 import 進來 -
export
與import
都可搭配as
取別名
Reference
John Resig, Secret of the JavaScript Ninja, 2rd
MDN, export
MDN, import
以上所述就是小编给大家介绍的《ECMAScript 之 Module》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。