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。
所以瀏覽器後來有提供 deferasync 屬性,來支持異步加載。
<!-- 页面内嵌的脚本 -->
<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>
Made with Slides.com