ES6 Module
Mia Yang
The single most important code organization pattern
in all of JavaScript is Module
# Traditional Module Pattern
// multiple
function traditional (name) {
function greeting () {
console.log("Hello " + name + "!");
}
return {
greeting
};
}
const Mia = traditional("Mia");
Mia.greeting()
// singleton
const Ian = (function (name) {
function greeting () {
console.log("Hello " + name + "!");
}
return { greeting }
})("Ian")
Ian.greeting()
# Moving Forward
File-Based Module (one module one file)
Static (better than CommonJS)
Singletons (reference to the one centralized instance)
Properties and methods are actual binding to the identifiers in the inner module definition
# CommonJS
Node.js 中的 Module 載入機制是,呼叫 require 時檢查載入的模組是否在快取中,如果在,則直接返回該 Module 的內容。如果不在,則建立該 Module 的 Instance,並將它儲存到快取中,以便下次require 使用。 這也意味著每次 require 時,都會得到相同的 Instance。
# export
// Way 1
export let firstName = 'Ian';
export let lastName = 'Huang';
export let year = '1997';
// Way 2
let firstName = 'Mia';
let lastName = 'Yang';
let year = 1998;
let greeting = function () {
console.log(`Hi, I'm ${firstName}`)
}
// Also can rename module memebr during named export
export { firstName, lastName, year, greeting as greetingFun }
# import
// module.js
let firstName = 'Mia';
let lastName = 'Yang';
let year = 1998;
let greeting = function () {
console.log(`Hi, I'm ${firstName}`)
}
export { firstName, lastName, year, greeting }
// main.js
import { firstName, lastName, year, greeting as greetingFun } from './module.js'
console.log(firstName, lastName, year)
greetingFun()
// -------- OUTPUT ---------
// Mia Yang 1998
// Hi I'm Mia
# Immutable / Read-only
import {a} from './xxx.js'
a = {}; // Syntax Error : 'a' is read-only;
// But, if a is object
import {a} from './xxx.js'
a.foo = 'hello'; // 合法操作
# Hoisting
foo();
import { foo } from 'my_module';
由於 import 是靜態分析(載入),所以在編譯階段就會先執行的,故有 Hoisting 的效果,也因為是靜態執行,所以 expression、variable、if condition 都是沒辦法在編譯階段得到實質的
// 报错
import { 'f' + 'oo' } from 'my_module';
// 报错
let module = 'my_module';
import { foo } from module;
// 报错
if (x === 1) {
import { foo } from 'module1';
} else {
import { foo } from 'module2';
}
# Entirely Import (*)
// main.js
// individually import
import { area, circumference } from './circle';
console.log('圆面积:' + area(4));
console.log('圆周长:' + circumference(14));
// Entirely import
import * as circle from './circle';
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));
// circle.js
export function area(radius) {
return Math.PI * radius * radius;
}
export function circumference(radius) {
return 2 * Math.PI * radius;
}
# export default
// module.js
// way 1
export default function () {
console.log("Hi I'm Mia")
}
// way2
function greeting () { /***/ }
export default greeting
// way3
function greeting () { /***/ }
export { greeting as default, sss}
// main.js
import greeting, { sss } from "./module.js"
greeting()
// ------ OUTPUT ------
// Hi I'm Mia
# Difference
// module.js
let greeting = 'Hi, Mia'
export default greeting
setTimeout(() => {
greeting = 'Hi, Ian'
}, 1000)
// main.js
import greeting from './module.js'
console.log(greeting)
setTimeout(() => {
console.log(greeting)
}, 3000)
// ------ OUTPUT -------
// Hi, Mia
// HI, Mia
// module.js
let greeting = 'Hi, Mia'
export { greeting as default }
setTimeout(() => {
greeting = 'Hi, Ian'
}, 1000)
// main.js
import greeting from './module.js'
console.log(greeting)
setTimeout(() => {
console.log(greeting)
}, 3000)
// ------ OUTPUT -------
// Hi, Mia
// HI, Ian
# import()
前面有提到說 export 和 import 為靜態分析(載入),這樣的設計雖有利於編輯器的效率,但也導致無法做到 require 一樣地條件載入,所以在 ES2020 中加入了 import()來支持動態的條件載入!
// case1
button.addEventListener('click', event => {
import('./dialogBox.js')
.then(dialogBox => {
dialogBox.open();
})
.catch(error => {
})
});
// case2
if (condition) {
import('moduleA').then(...);
} else {
import('moduleB').then(...);
}
# Browser Loading
默認預設的情況下,瀏覽器是同步加載 JS Script,這表示瀏覽器遇到 <script> 時會停下來載入並執行,再繼續渲染,如果是外部的 Script 還必須等下載的時間,如果 Script 檔案太大也有可能造成 UI Blocking。
所以瀏覽器後來有提供 defer 與 async 屬性,來支持異步加載。
<!-- 页面内嵌的脚本 -->
<script type="application/javascript">
// module code
</script>
<!-- 外部脚本 -->
<script type="application/javascript" src="path/to/myModule.js">
</script>
<script src="path/to/myModule.js" defer></script>
<script src="path/to/myModule.js" async></script>
# defer v.s async & type module
瀏覽器遇到這兩個屬性,就會開始載入腳本,但不會等它下載和執行,而兩者主要的區別是
defer: 渲染完就執行,如果有多個 defer 就會依續執行
async: 載入/下載完就執行,如果有多個 async 也不能保證順序
在 ES6 之後,新增了 type="module" 屬性
<script type="module" src="./foo.js"></script>
<!-- 等同於 -->
<script src="./foo.js" defer></script>
<!-- 當然也可以使用 async -->
<script type="module" src="./foo.js" async></script>