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 內: numClickshandleClick() 看似 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 並未提供 moduleexports ,因此還要透過 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 內: numClickshandleClick() 看似 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 有以下特色:

  • 提供 exportimport 兩個 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;

可直接對 letconst 變數加以 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 無法對 varletconst 使用 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;
  }
}

nameadd() 為 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 時,已經使用 asadd 取別名為 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 語法很簡單,只有 exportimport 兩個 keyword
  • ES6 Module 分成 Named Export 與 Default Export,一個 Module 只能有一個 Default Export,但可以有多個 Named Export
  • 可以使用 import * as module ,將整個 Module 都 import 進來
  • exportimport 都可搭配 as 取別名

Reference

John Resig, Secret of the JavaScript Ninja, 2rd

MDN, export

MDN, import


以上所述就是小编给大家介绍的《ECMAScript 之 Module》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

平台战略

平台战略

陈威如、余卓轩 / 中信出版社 / 2013-1 / 58.00元

《平台战略:正在席卷全球的商业模式革命》内容简介:平台商业模式的精髓,在于打造一个完善的、成长潜能强大的“生态圈”。它拥有独树一帜的精密规范和机制系统,能有效激励多方群体之间互动,达成平台企业的愿景。纵观全球许多重新定义产业架构的企业,我们往往就会发现它们成功的关键——建立起良好的“平台生态圈”,连接两个以上群体,弯曲、打碎了既有的产业链。 平台生态圈里的一方群体,一旦因为需求增加而壮大,另......一起来看看 《平台战略》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

URL 编码/解码
URL 编码/解码

URL 编码/解码