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.jsfunction sum(a, b) {  return a + b}function multiply(a, b) {  return a * b}module.exports = {  sum,  multiply}
  • 使用exports新增新的屬性並賦值
// math.jsfunction sum(a, b) {  return a + b}function multiply(a, b) {  return a * b}exports.sum = sumexports.multiply = multiply
  • 使用require引入
// main.jsconst math = require('./math.js')console.log(math.sum(1, 2)) // 輸出 3console.log(math.multiply(3, 4)) // 輸出 12

注意點

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

不適合用於瀏覽器

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

ES Module

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

用法

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

預設輸出

  • 輸出時使用default關鍵字,輸入時可使用任意名稱
  • 預設只能輸出一個東西
// data.jsfunction echo(msg){    console.log(msg)}let name ="這裡是data"echo(name)exprot default echo // export default 資料
// main.jsimport echo from './data.js'let name = '這裡是main'echo(name)//在HTML檔案中引入主要js檔案<script type="module" src="main.js"></script>
  • export 多個 function
//可以放在一個物件// data.jsfunction 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//可以自行命名importimport data from './data.js'console.log(data) // { echo:fn, add:fn }data.echo('main') // maindata.add(2, 4) // 6

具名輸入、輸出

  • 使用大括號包覆輸入、輸出的變數名稱
  • 輸出與輸入的名稱必須相同
//輸出語法export { 變數1, 變數2,... }//輸入語法import { 變數1, 變數2,... } from '模組檔案路徑'
export const a = 1export const b = 2import { a, b } from './xxx.js'
  • 同時使用預設、非預設輸出
// lib.jsconst x = 3const obj = { x: 3, y: 6 }const data = { name: 'Ron', age: 33 }export default xexport { obj, data }// 整合寫法export { x as default, obj, data }
// main.jsimport x from './lib.js'import { obj, data } from './lib.js'//整合寫法import x, { obj, data } from './lib.js'
  • 輸出多個 function 寫法
// lib.jsconst 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// 寫法1export function sayHello() {  return 'Hello World'}// 寫法2export default function () {  return 'Hello World'}
  • 按下按鈕後才載入lib.js
// main.jsdocument.getElementById('myButton').addEventListener('click', () => {  import('./lib.js')    .then((lib) => {      console.log(lib)      alert(lib.sayHello())    })    .catch((err) => {      console.error(err)    })})// 使用async awaitdocument.getElementById('myButton').addEventListener('click', async () => {  const lib = await import('./lib.js')  alert(lib.sayHello())})// 寫法2document.getElementById('myButton').addEventListener('click', async () => {  const lib = await import('./lib.js')  alert(lib.default())})

CommonJS 和 ESModule 的差異

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

參考資料

# JavaScript # CommonJS # ESModule