當web應用代碼規模越來越龐大的時候,我們會把代碼分離到多個文件中,稱為「模塊」。一個模塊通常是一個類或者多個函數組成的方法庫。
長期以來 JavaScript 是沒有模塊句法的,這不是一個問題,因為剛開始腳本比較少而且簡單,不需要模塊。但隨著代碼規模的增大,開始把代碼整理到模塊中,出現了一些庫:
AMD - 最早期的一個模塊規範,最開始由 require.js 實現了這個規範。CommonJS - Node.js 指定的規範UMD - 統一模塊規範,和 AMD, CommonJS 規範兼容。現在JS 有了「官方」的模塊系統,自 2015年成為規範之後逐步替換了上述幾種模塊,也已經在新版瀏覽器和 Node.js 得到了支持。
AMD 速覽
AMD是The Asynchronous Module Definition (AMD)的縮寫,這個規範主要用來定義模塊以實現模塊及其依賴可以異步加載。它的 API 也很簡單,一共2個:
define() 函數define(id?, dependencies?, factory);其中 id 和 dependencies 都是可選的。這個例子使用了3個參數:define("alpha", ["require", "exports", "beta"], function (require, exports, beta) { exports.verb = function() { return beta.verb(); //Or: return require("beta").verb(); }});這是一個只使用了一個參數的例子:define(function (require, exports, module) { var a = require('a'), b = require('b'); exports.action = function () {};});define.amd 屬性AMD規範要求具體的實現中 define 函數必須有一個 amd 屬性,主要目的是標識這個 define 函數是一個 AMD 的模塊,避免和已有的不遵循 AMD 規範的 define() 函數衝突。
CommonJS 速覽
CommonJS 主要用於伺服器端JS的規範,Node.js 採用此規範。
在模塊內部,有一個自由變量 require ,它是一個函數。在模塊內部,有一個自由變量 exports,它的格式是對象,可以把需要暴露給外部的屬性或者方法追加到此對象裡。在模塊內容,必須有一個自由變量 module,它是一個對象。例子1. 在一個模塊內部導出多個值:
// numbers.jsexports.a = 1exports.b = 2exports.c = 3exports.add = (x, y) => x + y
// test.jsconst { a, b, c, add } = require('./numbers.js'); console.log('a:', a);console.log('b:', b);console.log('c:', c);console.log('add(3,5):', add(3,5));
例子2.在一個模塊內部只導出一個值:
// numbers.jsmodule.exports = { a: 1, b: 2, c: 3, add: (x, y) => x + y}UMD 速覽
UMD (Universal Module Definition) 嚴格來說是一個設計模式,並不是一個規範。它嘗試兼容目前已有的幾種模塊規範方便加載,同時支持 AMD 和 CommonJS 規範。
(function (root, factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define([], factory); } else if (typeof module === 'object' && module.exports) { // Node. Does not work with strict CommonJS, but // only CommonJS-like environments that support module.exports, // like Node. module.exports = factory(); } else { // Browser globals (root is window) root.returnExports = factory(); }}(typeof self !== 'undefined' ? self : this, function () { // Just return a value to define the module export. // This example returns an object, but the module // can return a function as the exported value. return {};}));
ES module
對於 ESM 來說,一個模塊就是一個文件。模塊之間可以使用 export 和 import 導出和導入。
export 關鍵字可以把當前模塊中的變量或者函數暴露出來,方便被其他模塊使用import 關鍵字可以把其他模塊導入進來
import 句法:
import defaultExport from "module-name";import * as name from "module-name";import { export1 } from "module-name";import { export1 as alias1 } from "module-name";import { export1 , export2 } from "module-name";import { foo , bar } from "module-name/path/to/specific/un-exported/file";import { export1 , export2 as alias2 , [...] } from "module-name";import defaultExport, { export1 [ , [...] ] } from "module-name";import defaultExport, * as name from "module-name";import "module-name";var promise = import("module-name");
export 句法:
// Exporting individual featuresexport let name1, name2, …, nameN; // also var, constexport let name1 = …, name2 = …, …, nameN; // also var, constexport function functionName(){...}export class ClassName {...} // Export listexport { name1, name2, …, nameN }; // Renaming exportsexport { variable1 as name1, variable2 as name2, …, nameN }; // Exporting destructured assignments with renamingexport const { name1, name2: bar } = o; // Default exportsexport default expression;export default function (…) { … } // also class, function*export default function name1(…) { … } // also class, function*export { name1 as default, … }; // Aggregating modulesexport * from …; // does not set the default exportexport * as name1 from …;export { name1, name2, …, nameN } from …;export { import1 as name1, import2 as name2, …, nameN } from …;export { default } from …;
例如
// sayHi.jsexport function sayHi(user) { alert(`Hello, ${user}!`);}
在其他文件中可以引入此文件:
// main.jsimport {sayHi} from './sayHi.js'; alert(sayHi); // function...sayHi('John'); // Hello, John!
模塊主要的特點
模塊內部的代碼總是處於嚴格模式。use strict 模塊級別的作用域模塊之間的變量如果沒有export默認是相互隔離的,要解決上述問題,可以把變量導出一個模塊中的代碼只有在首次加載時執行import.meta在模塊內部 this 關鍵字是 undefined