[JS] JavaScript 模組(ES Module, ESM)
- 👍 Modules @ JavaScript.info
- JavaScript modules @ MDN
- ES Module Syntax @ Rollup
TL;DR
- module 的內容只有在第一次被 import 的時候會被執行(evaluated),而且它會是 singleton,也就是說,如果 export 的是物件,這個物件只會有一個,在任何 module 都會改到同一個物件(參考:A module code is evaluated only the first time when imported)。
- 如果是在瀏覽中使用 ESModule,則每個 script 都會以與
defer
相同的方式執行。
ESM
// default 後面直接是 JavaScript 物件,可以接 key-value
export default { foo: 'bar' };
import esm from './modules'; // { foo: 'bar' }
// named imports and exports
// 代表匯出 foo 和 bar 這兩個變數,不能用 key-value pair
export { foo, bar };
import { foo, bar } from './modules';
// re-export
export { foo, bar } from './modules';
實名匯出 - 直接定義變數並匯出
匯出:
// util.js
// 直接定義並匯出變數
export const deviceName = 'iPhone';
export const mobilesOnSale = ['Samsung', 'Apple', 'Huawei'];
export const offers = {
priceCurrency: 'TWD',
price: '26,900',
};
export const logPrice = (price) => {
console.log('price: ', price);
};
export function logDeviceName(deviceName) {
console.log(deviceName);
}
匯入的名稱需要和匯出時相對應
// 在另一隻檔案只需要使用 import{} 即可匯入
import { deviceName, mobilesOnSale, offers, logPrice, logDeviceName } from './utils';
實名匯出 - 先定義好變數再匯出
也可以先把變數定義好,接著透過 export {}
把變數匯出,這種做法可以在匯出時透過 as
修改匯出的名稱:
// util.js
// 先定義好變數後匯出
const deviceName = 'iPhone';
const mobilesOnSale = ['Samsung', 'Apple', 'Huawei'];
const offers = {
priceCurrency: 'TWD',
price: '26,900',
};
const logPrice = (price) => {
console.log('price: ', price);
};
function logDeviceName(deviceName) {
console.log(deviceName);
}
// 匯出時可以進行名稱的修改
export { deviceName, mobilesOnSale, offers as productDetail, logPrice, logDeviceName };
要特別留意這裡 export 後的大括號並不是物件,而是匯出用的語法,千萬不要在裡面使用 key-value 這樣的寫法!
匯入時同樣只需要根據匯出時的變數對應使用 import{ }
即可:
// 在另一隻檔案只需要使用 import{ } 即可匯入
import {
deviceName as device,
productDetail, // 要用匯出 時的名稱
} from './utils';
預設匯出(Default Export)
當模組只有一個單一的 export 時才建議使用 export default
,雖然可以但不建議同時使用 default export 又使用 named export。
預設匯出的語法是在 export
後加上 default
,default
後則直接帶入你想要匯出的東西即可,例如:個變數:
const logPrice = (price) => {
console.log('price: ', price);
};
export default logPrice;
匯入的時候可以自己隨意取名,這裡我們取做 showPrice
,就可以直接使用:
import showPrice from './utils';
showPrice(1000);
要特別留意的是,和實名匯出不同,如果你在 export default
後接的是 {}
,這個 {}
表示的就是物件,裡面放的就會是物件的屬性名稱和屬性值,因此不能再用 as
去修改名稱,例如:
const deviceName = 'iPhone';
const mobilesOnSale = ['Samsung', 'Apple', 'Huawei'];
const logPrice = (price) => {
console.log('price: ', price);
};
// export default 後直接帶入要匯出的東西
// 這裡是會直接匯出「物件」,因此不能在裡面使用 "as" 語法
export default {
deviceName, // deviceName: deviceName 的縮寫
mobilesOnSale, // mobilesOnSale: mobilesOnSale 的縮寫
logPrice, // logPrice: logPrice 的縮寫
};
匯入時也會是一個物件,若要使用裡面的資料,需要使用物件的方式來操作:
// myPhone 會是物件
import myPhone from './utils';
// 透過物件的方式操作
console.log('Device:', myPhone.deviceName);
// 也可以透過解構賦值將需要的屬性取出
const { logPrice } = myPhone;
logPrice(24900);
和實名匯出的 export {}
不同, export default {}
後的 {}
表示的真的就是個物件。匯入時也是匯入整個物件。
匯入一整個模組 import module
import 'lodash'; // 「執行」 lodash module 中內容,且不存成變數
import _ from 'lodash'; // 載入 default 並取名為 _
import { map, reduce } from 'lodash'; // 只載入特定模組
import { map as _map } from 'lodash'; // 載入特定模組並重新命名
import _, { map } from 'lodash'; // 同時載入 default 與特定模組
import { default as _, map } from 'lodash'; // 效果同上
import * as _ from 'lodash'; // 載入全部的模組,並放到名為 _ 的 namespace 中
// 動態載入
import('./modules.js').then(({ default: DefaultExport, NamedExport }) => {
// do something with modules.
});
動態載入(Dynamic Import)
動態載入(Dynamic Import) @ pjchender > Webpack 學習筆記
觀念
- 在模組內的程式總是會套用
strict mode
(因此不需要額外定義"use strict"
) - 模組並沒有共享全域空間,每一個模組都有一個屬於它自己的作用域(scope),和其他模組溝通時,需要透過
export
來暴露變數,沒有匯出的函式是無法使用的。
greet.js
// greet.js
function greet() {
console.log('Hello');
}
greet();
app.js
// app.js
/**
* 執行 app.js 時會出現 'Hello'
* 但是在 app.js 中並沒有辦法呼叫到 greet() 這個函式
**/
require('./greet.js');
基本語法
模組的匯入與匯出
匯出:使用 export {...}
// 匯出 calc.js
var str = 'Michael';
var fn = (x, y) => x * y;
var num = 1958;
export { str, fn, num };
匯入: 搭配 import {...} from ...
,大括號裡面的變量名,必須與被匯入的模組接口名稱相同
// 匯入,{ } 裡的變量名要與被匯入的 module 中的變數對應
import { str, fn, num } from './calc';
console.log('import', str, num);
console.log(fn(3, 4));
另外,匯入 module 時實際上仍參照到原本的變數,因此呼叫 onSale
時,price
的值會改變:
// 匯出 export.js
let price = 1000;
function onSale() {
price *= 0.9;
}
export { price, onSale };
// 匯入
import { price, onSale } from './export';
console.log('price', price); // 1000
onSale();
console.log('price', price); // 900
onSale();
console.log('price', price); // 810
as:為變數建立重新命名
匯出或匯入時均可使用 as
重新命名模組:
// 匯出
var str = 'Michael';
var fn = (x, y) => x * y;
var num = 1958;
export { str as name, fn, num as year };
// 匯入
import { name, fn as multiply, year } from './calc';
console.log('import', name, year);
console.log(multiply(3, 2));
default
當匯入的 module 具有 default
時,就不需要知道原模組中輸出的函數名:
- 不論匯出的 function 有無名稱,匯入時都視為匿名函式;匯入時可以為該匿名函數指定任意名字
- 匯入時不使用大括號
{ }
(一般的匯入在匯入時需使用{ }
指定要匯入的變數名) export default
命令其實只是輸出一個as default
的變量
// 匯出
var multiply = (x, y) => x * y;
export default multiply; // 等同於 export { multiply as default }
// 匯入
/**
* 等同於 import { default as multiply } from './calc';
* 因為是 "as" 所以名稱可以自取,不用是 multiply
**/
import multiply from './calc'; //
console.log(multiply(3, 2));
- 由於
export default
命令其實只是輸出一個as default
的變量,所以它後面不能跟變量聲明語句:
// 正確
export var a = 1;
// 正確
var a = 1;
export default a;
// 錯誤
export default var a = 1;
另外,可以同時使用 export { }
和 export default
來同時匯出 default 和特定內容:
// 匯出 export.js
let price = 1000;
function onSale() {
price *= 0.9;
}
let defaultText = 'This is default text';
export { price, onSale };
export default defaultText;
// export { defaultText as default, price, onSale} // 等同於上面兩句
// 匯入
import defaultValue, { price, onSale } from './export';
console.log('defaultValue', defaultValue); // This is default text
console.log('price', price); // 1000
onSale();
console.log('price', price); // 900
import * as ...: 匯入該模組中的所有內容
// 匯出
var str = 'Michael';
var fn = (x, y) => x * y;
var num = 1958;
export { str, fn, num };
匯入的內容會是一包物件:
// 匯入
import * as calc from './calc'; // calc 會是一包物件
console.log('import', calc.str, calc.num);
console.log(calc.fn(2, 6));