JavaScript-模組化

簡介

  1. 單一程式切割成多個檔案,每個程式檔案被稱為模組
  2. 模組有各自獨立的名稱空間,互不影響(可以在不同模組命名相同的變數名稱)
  3. 在開發大型複雜應用程式時,將重複性高的程式碼抽出模組化,更為方便且易於管理

script 要加上 type=“module” , 引入檔案的副檔名記得加上.js

<script type="module">import message from "./message.js";</script>

Common JS

  • 主要用於 Node.js(伺服器環境)
  • 在 ES6 之前,JS 模組化的非官方社群規範

用法

  • 一個.js是一個模組
  • 使用 module.exports 輸出整個模組或某個功能
  • 使用 exports 來輸出多個值
  • 使用 require 函數來載入模組

範例

  • 使用module.exports輸出
// math.js

function sum(a, b) {
  return a + b
}

function multiply(a, b) {
  return a * b
}

module.exports = {
  sum,
  multiply
}
  • 使用exports新增新的屬性並賦值
// math.js

function sum(a, b) {
  return a + b
}

function multiply(a, b) {
  return a * b
}

exports.sum = sum
exports.multiply = multiply
  • 使用require引入
// main.js

const math = require('./math.js')

console.log(math.sum(1, 2)) // 輸出 3
console.log(math.multiply(3, 4)) // 輸出 12

注意點

  • CommonJS 模組是同步加載的
  • 著當 Node.js 遇到 require() 語句時,會停下來等待模組加載和處理完成,然後才繼續執行後續代碼

不適合用於瀏覽器

  • 當瀏覽器加載一個模組時,它會阻塞頁面的其他腳本執行,直到模組加載和處理完畢
  • 會導致瀏覽器頁面渲染延遲,降低用戶體驗

ES Module

  • 是 ES6 引入的官方標準化的 JavaScript 模組系統
  • 可用於 Node.js(伺服器端)或是瀏覽器(客戶端)

用法

  • 使用 import 關鍵字導入模組
  • 使用 export 輸出模組
  • 需要在<script>標籤上加上type = "module"

預設輸出

  • 輸出時使用default關鍵字,輸入時可使用任意名稱
  • 預設只能輸出一個東西
// data.js
function echo(msg){
    console.log(msg)
}
let name ="這裡是data"
echo(name)

exprot default echo // export default 資料
// main.js
import echo from './data.js'
let name = '這裡是main'
echo(name)

//在HTML檔案中引入主要js檔案
<script type="module" src="main.js"></script>
  • export 多個 function
//可以放在一個物件
// data.js
function echo(msg) {
  console.log(msg)
}
function add(a, b) {
  console.log(a + b)
}

//如果物件的屬性名稱和值名稱相同,可以省略屬性名稱,直接寫出值
// export default {
//     echo:echo,
//     add:add
// }
export default {
  echo,
  add
}
// main.js
//可以自行命名import
import data from './data.js'

console.log(data) // { echo:fn, add:fn }
data.echo('main') // main
data.add(2, 4) // 6

具名輸入、輸出

  • 使用大括號包覆輸入、輸出的變數名稱
  • 輸出與輸入的名稱必須相同
//輸出語法
export { 變數1, 變數2,... }
//輸入語法
import { 變數1, 變數2,... } from '模組檔案路徑'
export const a = 1
export const b = 2

import { a, b } from './xxx.js'
  • 同時使用預設、非預設輸出
// lib.js
const x = 3
const obj = { x: 3, y: 6 }
const data = { name: 'Ron', age: 33 }
export default x
export { obj, data }

// 整合寫法
export { x as default, obj, data }
// main.js
import x from './lib.js'
import { obj, data } from './lib.js'

//整合寫法
import x, { obj, data } from './lib.js'
  • 輸出多個 function 寫法
// lib.js
const add = (a, b) => {
  console.log(a + b)
}
const multiply = (a, b) => {
  console.log(a * b)
}
const math = { add, multiply }

export default math
// main.js
//載入所有功能 - 使用預設輸入
import math from './lib.js'
math.add(1, 2)
math.multiply(3, 4)

//載入個別功能
import { add } from './lib.js'
add(1, 2)
  • 用*載入所有資料
import * as stringFunctions from './string_functions.js'
stringFunctions.uppercaseString('hello')
stringFunctions.lowercaseString('WORLD!')

靜態載入

  • 一般使用import關鍵字,是靜態載入
  • 在編譯階段,程式運行前就已經確定和載入要使用的模組
  • 有利於優化,例如樹搖(tree-shaking)

動態載入

  • 使用import()語法,回傳一個Promise
  • 在程式執行階段進行,可以根據條件或某些運行後的計算來決定是否載入模組
  • 可用於按需載入代碼分割懶加載

範例

// lib.js

// 寫法1
export function sayHello() {
  return 'Hello World'
}

// 寫法2
export default function () {
  return 'Hello World'
}
  • 按下按鈕後才載入lib.js
// main.js
document.getElementById('myButton').addEventListener('click', () => {
  import('./lib.js')
    .then((lib) => {
      console.log(lib)
      alert(lib.sayHello())
    })
    .catch((err) => {
      console.error(err)
    })
})

// 使用async await
document.getElementById('myButton').addEventListener('click', async () => {
  const lib = await import('./lib.js')
  alert(lib.sayHello())
})

// 寫法2
document.getElementById('myButton').addEventListener('click', async () => {
  const lib = await import('./lib.js')
  alert(lib.default())
})

CommonJS 和 ESModule 的差異

  • CommonJS 模組輸出的是一個值的拷貝,ESModule 輸出的是值的引用
  • CommonJS 模組是執行時載入,ESModule 模組是編譯時輸出介面

參考資料

# JavaScript # CommonJS # ESModule