Skip to main content

[JS] JavaScript 模組(ES Module)

TL;DR#

// default 後面直接是 JavaScript 物件,可以接 key-value
export default { foo: 'bar' };
// 代表匯出 foo 和 bar 這兩個變數,不能用 key-value pair
export { foo, bar };

實名匯出 - 直接定義變數並匯出#

匯出:

// 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 後加上 defaultdefault 後則直接帶入你想要匯出的東西即可,例如:個變數:

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);

匯入一整個模組 import module#

import 'lodash'; // 「執行」 lodash module 中內容,且不存成變數
import _ from 'lodash'; // 載入 default 並取名為 _
import { map, reduce } from 'lodash'; // 只載入特定模組
import { map as _map } from 'lodash'; // 載入特定模組並重新命名
import { default as _, map } from 'lodash'; // 同時載入 default 與特定模組
import _, { map } from 'lodash'; // 效果同上
import * as _ from 'lodash'; // 載入全部的模組,並放到名為 _ 的 namespace 中
// 動態載入
import('./modules.js').then(({ default: DefaultExport, NamedExport }) => {
// do something with modules.
});

觀念#

Imgur

  • 在模組內的程式總是會套用 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));

匯入(執行)模組但不賦予變數#

使用 import './module' 可以匯入(執行)模組但不賦予變數:

// 匯出
var str = 'Michael';
var fn = (x, y) => x * y;
var num = 1958;
console.log('calc'); // 這行會被執行
export { str, fn, num };
import 'lodash'; // 載入(執行)整個模組但不賦予變數
import './calc'; //
console.log('import', num); // undefined

Code Style#

當從同一個路徑匯入時,不要拆成多個 import#

@ airbnb 10.4

// bad
import foo from 'foo';
import { named1, named2 } from 'foo';
// good
import foo, { named1, named2 } from 'foo';

將所有的 import 放在最上方#

@ airbnb 10.7

由於 import 具有變量提升(hoisted)的特性,因此應該放在文件最上方:

// bad
import foo from 'foo';
foo.init();
import bar from 'bar';
// good
import foo from 'foo';
import bar from 'bar';
foo.init();

命名規則#

  • 小寫駝峰(camelCase):使用 export-default 時使用小寫駝峰命名與建檔
  • 大寫駝峰(PascalCase):當匯出的是純物件(pure object)、建構式(constructor)、類(class)、singleton 或 function library:

23.7 @ airbnb

檔名應該和 export default 的名稱相同#

// file 1 contents
class CheckBox { ... }
export default CheckBox;
// file 2 contents
export default function fortyTwo() { return 42; }
// file 3 contents
export default function insideDirectory() {}
import CheckBox from './CheckBox';
import fortyTwo from './fortyTwo';
import insideDirectory from './insideDirectory';

23.6 A base filename should exactly match the name of its default export. @ airbnb

Common JS 用法#

模組的匯入#

// ES6 寫法:用 import
import { str, fn, num } from './calc';
// CommonJS 寫法:用 require
/**
* require 只會載入檔案一次然後存放在記憶體裡面, 所以不用怕效能的問題
* 如果載入的是元件,開頭不用加 './',如果載入的是檔案或路徑,需要加上 './'
* 如果載入的是檔案沒寫副檔名,預設會載 .js 檔
**/
const something = require('./something'); // 找不到 ./something 時自動尋找 ./something.js
const something = require('./something.js');
const something = require('something'); // 匯入 npm 模組

模組的匯出#

/**
* ES6 寫法:用 export
**/
// profile.js
var str = 'Michael';
var fn = (x, y) => x * y;
var num = 1958;
export { str, fn, num };
/**
* CommonJS 寫法:用 module.exports
**/
const fn (options) = {
return (req, res, next) => {
// do something here
// console.log(options.name)
return next()
}
}
module.exports = fn

其他範例#

ES6#

// ES6 匯出
const sum = (a, b) => a + b;
const multiply = (a, b) => a + b;
export default { sum, multiply };
export { sum, multiply };
// ES6 匯入
import math from './math'; // ES6,沒有用 { } ,表示匯入 default 內容
console.log(math.sum(3, 5));
console.log(math.multiply(3, 5));

Common JS#

// 匯出
const sum = (a, b) => a + b;
const multiply = (a, b) => a + b;
module.exports.sum = sum; // CommonJS
module.exports.multiply = multiply;
module.exports = { sum, multiply }; // CommonJS
// 匯入
const math = require('./math');
const sum = require('./math').sum;
const multiply = require('./math').multiply;
math.sum(3, 5);
math.multiply(3, 5);

匯出 Constructor#

匯出前先 new#

// Common JS 匯出 constructor
function SumIndex() {
this.index = 2;
this.sum = function () {
console.log(this.index + 2);
};
}
module.exports = new SumIndex();
// Common JS 匯入
const math = require('./math'); // 但是如果之後在重新載入這個 module,並不會在重新引用一次
math.sum();

匯出整個 class#

// Common JS 匯出 constructor
function SumIndex() {
this.index = 2;
this.sum = function () {
console.log(this.index + 2);
};
}
module.exports = SumIndex;
// Common JS 匯入
const Math = require('./math'); // 但是如果之後在重新載入這個 module,並不會在重新引用一次
let math = new Math();
math.sum();

參考資料#

Last updated on