關鍵詞: AMD、CMD、UMD、CommonJS、ES Modulejavascript
規範JavaScript的模塊定義和加載機制,下降學習和使用各類框架的門檻,可以以一種統一的方式去定義和使用模塊,提升開發效率,下降了應用維護成本。html
目錄:前端
想當初,Brendan Eich 只用了十天就創造了 JavaScript 這門語言,誰曾想這門一直被看做玩具性質的語言在近幾年得到了爆發性地發展,從瀏覽器端擴展到服務器,再到 native 端,變得愈來愈火熱。而這門語言創造當初的諸多限制也在前端工程化的今天被放大,社區也在積極推進其變革。實現模塊化的開發正是其中最大的需求,本文梳理 JavaScript 模塊化開發的歷史和將來,以做學習之用。java
JavaScript 模塊化的發展歷程,是以 2009 年 CommonJS 的出現爲分水嶺,這一規範極大地推進前端發展。在1999年至2009年期間,模塊化探索都是基於語言層面的優化,2009 年後前端開始大量使用預編譯。node
在 1999 年的時候,那會尚未全職的前端工程師,寫 JS 是直接將變量定義在全局,作的好一些的或許會作一些文件目錄規劃,將資源歸類整理,這種方式被稱爲直接定義依賴,舉個例子:jquery
// greeting.js var helloInLang = { en: 'Hello world!', es: '¡Hola mundo!', ru: 'Привет мир!' }; function writeHello(lang) { document.write(helloInLang[lang]); } // third_party_script.js function writeHello() { document.write('The script is broken'); } 複製代碼
// index.html <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Basic example</title> <script src="./greeting.js"></script> <script src="./third_party_script.js"></script> </head> 複製代碼
可是,即便有規範的目錄結構,也不能避免由此而產生的大量全局變量,這就致使了一不當心就會有變量衝突的問題,就比如上面這個例子中的 writeHello
。webpack
因而在 2002 年左右,有人提出了命名空間模式的思路,用於解決遍地的全局變量,將須要定義的部分歸屬到一個對象的屬性上,簡單修改上面的例子,就能實現這種模式:git
// greeting.js var app = {}; app.helloInLang = { en: 'Hello world!', es: '¡Hola mundo!', ru: 'Привет мир!' }; app.writeHello = function (lang) { document.write(helloInLang[lang]); } // third_party_script.js function writeHello() { document.write('The script is broken'); } 複製代碼
不過這種方式,毫無隱私可言,本質上就是全局對象,誰均可以來訪問而且操做,一點都不安全。es6
2003 年左右就有人提出利用 IIFE 結合 Closures 特性,以此解決私有變量的問題,這種模式被稱爲閉包模塊化模式:github
// greeting.js var greeting = (function() { var module = {}; var helloInLang = { en: 'Hello world!', es: '¡Hola mundo!', ru: 'Привет мир!', }; module.getHello = function(lang) { return helloInLang[lang]; }; module.writeHello = function(lang) { document.write(module.getHello(lang)); }; return module; })(); 複製代碼
IIFE 能夠造成一個獨立的做用域,其中聲明的變量,僅在該做用域下,從而達到實現私有變量的目的,就如上面例子中的 helloInLang
,在該 IIFE 外是不能直接訪問和操做的,能夠經過暴露一些方法來訪問和操做,好比說上面例子裏面的 getHello
和 writeHello2
個方法,這就是所謂的 Closures。
同時,不一樣模塊之間的引用也能夠經過參數的形式來傳遞:
// x.js // @require greeting.js var x = (function(greeting) { var module = {}; module.writeHello = function(lang) { document.write(greeting.getHello(lang)); }; return module; })(greeting); 複製代碼
此外使用 IIFE,還有2個好處:
除了這些方式,還有其餘的如模版依賴定義、註釋依賴定義、外部依賴定義,不是很常見,但其本質都是想在語言層面解決模塊化的問題。
不過,這些方案,雖然解決了依賴關係的問題,可是沒有解決如何管理這些模塊,或者說在使用時清晰描述出依賴關係,這點仍是沒有被解決,能夠說是少了一個管理者。
沒有管理者的時候,在實際項目中,得手動管理第三方的庫和項目封裝的模塊,就像下面這樣把全部須要的 JS 文件一個個按照依賴的順序加載進來:
<script src="zepto.js"></script> <script src="jhash.js"></script> <script src="fastClick.js"></script> <script src="iScroll.js"></script> <script src="underscore.js"></script> <script src="handlebar.js"></script> <script src="datacenter.js"></script> <script src="deferred.js"></script> <script src="util/wxbridge.js"></script> <script src="util/login.js"></script> <script src="util/base.js"></script> <script src="util/city.js"></script> 複製代碼
對於這個問題,社區出現了新的工具,如 LABjs、YUI。YUI 做爲昔日前端領域的佼佼者,很好的糅合了命名空間模式及沙箱模式,如如下的例子:
// YUI - 編寫模塊 YUI.add('dom', function(Y) { Y.DOM = { ... } }) // YUI - 使用模塊 YUI().use('dom', function(Y) { Y.DOM.doSomeThing(); // use some methods DOM attach to Y }) // hello.js YUI.add('hello', function(Y){ Y.sayHello = function(msg){ Y.DOM.set(el, 'innerHTML', 'Hello!'); } },'3.0.0',{ requires:['dom'] }) // main.js YUI().use('hello', function(Y){ Y.sayHello("hey yui loader"); }) 複製代碼
YUI 團隊還提供的一系列用於 JS 壓縮、混淆、請求合併(合併資源須要 server 端配合)等性能優化的工具,說其是現有 JS 模塊化的鼻祖一點都不過度。
不過隨着 Node.js 的到來,新出的 CommonJS 規範的落地,以及各類前端工具、解決方案的出現,才真正使得前端開發大放光芒。
CommonJS 的出現真正使得前端進入工業化時代。前面說了,2009 年之前的各類模塊化方案雖然始終停留在語言層面上,雖然也有 YUI 這樣的工具,但還不足以成爲引領潮流的工具。究其緣由,仍是由於前端工程複雜度還沒積累到必定程度,隨着 Node.js 的出現,JS 涉足的領域轉向後端,加上 Web app 變得愈來愈複雜,工程發展到必定階段,要出現的必然會出現。
CommonJS 是一套同步的方案,它考慮的是在服務端運行的Node.js,主要是經過 require
來加載依賴項,經過 exports
或者 module.exports
來暴露接口或者數據的方式。
因爲在服務端能夠直接讀取磁盤上的文件,因此能作到同步加載資源,但在瀏覽器上是經過 HTTP 方式獲取資源,複雜的網絡狀況下沒法作到同步,這就致使必須使用異步加載機制。這裏發展出兩個有影響力的方案:
它們分別在瀏覽器實現了define
、require
及module
的核心功能,雖然二者的目標是一致的,可是實現的方式或者說是思路,仍是有些區別的,AMD 偏向於依賴前置,CMD 偏向於用到時才運行的思路,從而致使了依賴項的加載和運行時間點會不一樣。
// CMD define(function (require) { var a = require('./a'); // <- 運行到此處纔開始加載並運行模塊a var b = require('./b'); // <- 運行到此處纔開始加載並運行模塊b // more code .. }) 複製代碼
// AMD define( ['./a', './b'], // <- 前置聲明,也就是在主體運行前就已經加載並運行了模塊a和模塊b function (a, b) { // more code .. } ) 複製代碼
這裏也有很多爭議的地方,在於 CommonJS 社區認爲 AMD 模式破壞了規範,反觀 CMD 模式,簡單的去除 define
的外包裝,這就是標準的 CommonJS 實現,因此說 CMD 是最貼近 CommonJS 的異步模塊化方案。不過 AMD 的社區資源比 CMD 更豐富,這也是 AMD 更加流行的一個緣由。
此外同一時期還出現了一個 UMD 的方案,其實它就是 AMD 與 CommonJS 的集合體,經過 IIFE 的前置條件判斷,使一個模塊既能夠在瀏覽器運行,也能夠在 Node.js 中運行,舉個例子:
// UMD (function(define) { define(function () { var helloInLang = { en: 'Hello world!', es: '¡Hola mundo!', ru: 'Привет мир!' }; return { sayHello: function (lang) { return helloInLang[lang]; } }; }); }( typeof module === 'object' && module.exports && typeof define !== 'function' ? function (factory) { module.exports = factory(); } : define )); 複製代碼
不過這個用的比較少,僅做了解。
2015年6月,ECMAScript2015 發佈了,JavaScript 終於在語言標準的層面上,實現了模塊功能,使得在編譯時就能肯定模塊的依賴關係,以及其輸入和輸出的變量,不像 CommonJS、AMD 之類的須要在運行時才能肯定,成爲瀏覽器和服務器通用的模塊解決方案。
// lib/greeting.js const helloInLang = { en: 'Hello world!', es: '¡Hola mundo!', ru: 'Привет мир!' }; export const getHello = (lang) => ( helloInLang[lang]; ); export const sayHello = (lang) => { console.log(getHello(lang)); }; // hello.js import { sayHello } from './lib/greeting'; sayHello('ru'); 複製代碼
與 CommonJS 用 require()
方法加載模塊不一樣,在 ES Module 中,import
命令能夠具體指定加載模塊中用 export
命令暴露的接口(不指定具體的接口,默認加載 export default
),沒有指定的是不會加載的,所以會在編譯時就完成模塊的加載,這種加載方式稱爲編譯時加載或者靜態加載。
而 CommonJS 的 require()
方法是在運行時才加載的:
// lib/greeting.js const helloInLang = { en: 'Hello world!', es: '¡Hola mundo!', ru: 'Привет мир!' }; const getHello = function (lang) { return helloInLang[lang]; }; exports.getHello = getHello; exports.sayHello = function (lang) { console.log(getHello(lang)) }; // hello.js const sayHello = require('./lib/greeting').sayHello; sayHello('ru'); 複製代碼
能夠看出,CommonJS 中是將整個模塊做爲一個對象引入,而後再獲取這個對象上的某個屬性。
所以 ES Module 的編譯時加載,在效率上面會提升很多,此外,還會帶來一些其它的好處,好比引入宏(macro)和類型檢驗(type system)這些只能靠靜態分析實現的功能。
不過因爲 ES Module 在低版本的 Node.js 和瀏覽器上支持度有待增強,因此通常仍是經過 Babel 進行轉換成 es5 的語法,兼容更多的平臺。
Node 應用由模塊組成,採用 CommonJS 模塊規範。
每一個文件就是一個模塊,有本身的做用域。在一個文件裏面定義的變量、函數、類,都是私有的,對其餘文件不可見。
CommonJS 規範規定,每一個模塊內部,module
變量表明當前模塊。這個變量是一個對象,它的 exports
屬性(即 module.exports
)是對外的接口。加載某個模塊,實際上是加載該模塊的 module.exports
屬性。
var x = 5; var addX = function (value) { return value + x; }; module.exports.x = x; module.exports.addX = addX; 複製代碼
require
方法用於加載模塊。
var example = require('./example.js'); console.log(example.x); // 5 console.log(example.addX(1)); // 6 複製代碼
Node 內部提供一個 Module
構建函數。全部模塊都是 Module
的實例。
function Module(id, parent) { this.id = id; this.exports = {}; this.parent = parent; // ... } 複製代碼
每一個模塊內部,都有一個 module
對象,表明當前模塊。它有如下屬性:
module.id
模塊的識別符,一般是帶有絕對路徑的模塊文件名。module.filename
模塊的文件名,帶有絕對路徑。module.loaded
返回一個布爾值,表示模塊是否已經完成加載。module.parent
返回一個對象,表示調用該模塊的模塊。module.children
返回一個數組,表示該模塊要用到的其餘模塊。module.exports
表示模塊對外輸出的值module.exports
屬性表示當前模塊對外輸出的接口,其餘文件加載該模塊,實際上就是讀取 module.exports
變量。
爲了方便,Node 爲每一個模塊提供一個 exports
變量,指向 module.exports
。這等同在每一個模塊頭部,有一行這樣的命令:
var exports = module.exports; 複製代碼
形成的結果是,在對外輸出模塊接口時,能夠向 exports
對象添加方法。
exports.area = function (r) { return Math.PI * r * r; }; exports.circumference = function (r) { return 2 * Math.PI * r; }; 複製代碼
注意,不能直接將 exports
變量指向一個值,由於這樣等於切斷了 exports
與 module.exports
的聯繫。
// 無效代碼 exports.hello = function() { return 'hello'; }; module.exports = 'Hello world'; 複製代碼
上面代碼中,hello
函數是沒法對外輸出的,由於 module.exports
被從新賦值了。
這意味着,若是一個模塊的對外接口,就是一個單一的值,不能使用 exports
輸出,只能使用 module.exports
輸出。
module.exports = function (x){ console.log(x);}; 複製代碼
一般,咱們會把相關的文件會放在一個目錄裏面,便於組織。這時,最好爲該目錄設置一個入口文件,讓 require
方法能夠經過這個入口文件,加載整個目錄。
在目錄中放置一個 package.json
文件,而且將入口文件寫入 main
字段。下面是一個例子。
// package.json { "name" : "some-library", "main" : "./lib/some-library.js" } 複製代碼
require
發現參數字符串指向一個目錄之後,會自動查看該目錄的 package.json
文件,而後加載 main
字段指定的入口文件。若是 package.json
文件沒有 main
字段,或者根本就沒有 package.json
文件,則會加載該目錄下的 index.js
文件或 index.node
文件。
第一次加載某個模塊時,Node會緩存該模塊。之後再加載該模塊,就直接從緩存取出該模塊的 module.exports
屬性。
require('./example.js'); require('./example.js').message = "hello"; require('./example.js').message // "hello" 複製代碼
上面代碼中,連續三次使用 require
命令,加載同一個模塊。第二次加載的時候,爲輸出的對象添加了一個 message
屬性。可是第三次加載的時候,這個 message
屬性依然存在,這就證實 require
命令並無從新加載模塊文件,而是輸出了緩存。
若是想要屢次執行某個模塊,可讓該模塊輸出一個函數,而後每次 require
這個模塊的時候,從新執行一下輸出的函數。
全部緩存的模塊保存在 require.cache
之中,若是想刪除模塊的緩存,能夠像下面這樣寫。
// 刪除指定模塊的緩存 delete require.cache[moduleName]; // 刪除全部模塊的緩存 Object.keys(require.cache).forEach(function(key) { delete require.cache[key]; }) 複製代碼
注意,緩存是根據絕對路徑識別模塊的,若是一樣的模塊名,可是保存在不一樣的路徑,require
命令仍是會從新加載該模塊。
CommonJS 模塊的加載機制是,輸入的是被輸出的值的拷貝。也就是說,一旦輸出一個值,模塊內部的變化就影響不到這個值。請看下面這個例子。
下面是一個模塊文件lib.js
。
// lib.js var counter = 3; function incCounter() { counter++; } module.exports = { counter: counter, incCounter: incCounter, }; 複製代碼
上面代碼輸出內部變量 counter
和改寫這個變量的內部方法 incCounter
。
而後,加載上面的模塊。
// main.js var counter = require('./lib').counter; var incCounter = require('./lib').incCounter; console.log(counter); // 3 incCounter(); console.log(counter); // 3 複製代碼
上面代碼說明,counter
輸出之後,lib.js
模塊內部的變化就影響不到 counter
了。
AMD 全稱爲 Asynchromous Module Definition(異步模塊定義)。 AMD 是 RequireJS 在推廣過程當中對模塊定義的規範化產出,它是一個在瀏覽器端模塊化開發的規範。 AMD 模式能夠用於瀏覽器環境而且容許異步加載模塊,同時又能保證正確的順序,也能夠按需動態加載模塊。
模塊經過 define
函數定義在閉包中,格式以下:
define(id?: String, dependencies?: String[], factory: Function|Object); 複製代碼
id
是模塊的名字,它是可選的參數。
dependencies
指定了所要依賴的模塊列表,它是一個數組,也是可選的參數,每一個依賴的模塊的輸出將做爲參數一次傳入 factory
中。若是沒有指定 dependencies
,那麼它的默認值是 ["require", "exports", "module"]
:
define(function(require, exports, module) {}) 複製代碼
factory
是最後一個參數,它包裹了模塊的具體實現,它是一個函數或者對象。若是是函數,那麼它的返回值就是模塊的輸出接口或值。
用例:
定義一個名爲 myModule 的模塊,它依賴 jQuery 模塊:
// 定義 define('myModule', ['jquery'], function($) { // $ 是 jquery 模塊的輸出 $('body').text('hello world'); }); // 使用 require(['myModule'], function(myModule) {}); 複製代碼
定義一個沒有 id 值的匿名模塊,一般做爲應用的啓動函數:
define(['jquery'], function($) { $('body').text('hello world'); }); 複製代碼
依賴多個模塊的定義:
define(['jquery', './math.js'], function($, math) { // $ 和 math 一次傳入 factory $('body').text('hello world'); }); 複製代碼
模塊輸出:
define(['jquery'], function($) { var HelloWorldize = function(selector){ $(selector).text('hello world'); }; // HelloWorldize 是該模塊輸出的對外接口 return HelloWorldize; }); 複製代碼
在模塊定義內部引用依賴:
define(function(require) { var $ = require('jquery'); $('body').text('hello world'); }); 複製代碼
RequireJS 能夠看做是對 AMD 規範的具體實現,它的用法和上節所展現的有所區別。
下載地址:requirejs.org/docs/downlo…
下面簡單介紹一下其用法:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>requirejs test</title> </head> <body> <div id="messageBox"></div> <button id="btn" type="button" name="button">點擊</button> <script data-main="js/script/main.js" src="js/lib/require.js"></script> </body> </html> 複製代碼
這裏的 script
標籤,除了指定 RequireJS 路徑外,還有個 data-main
屬性,這屬性指定在加載完 RequireJS 後,就用 RequireJS 加載該屬性值指定路徑下的 JS 文件並運行,因此通常該 JS 文件稱爲主 JS 文件(其 .js 後綴能夠省略)。
// 配置文件 require.config({ baseUrl: 'js', paths: { jquery: 'lib/jquery-1.11.1', } }); // 加載模塊 require(['jquery', 'script/hello'],function ($, hello) { $("#btn").click(function(){ hello.showMessage("test"); }); }); 複製代碼
// 定義模塊 define(['jquery'],function($){ //變量定義區 var moduleName = "hello module"; var moduleVersion = "1.0"; //函數定義區 var showMessage = function(name){ if(undefined === name){ return; }else{ $('#messageBox').html('歡迎訪問 ' + name); } }; //暴露(返回)本模塊API return { "moduleName":moduleName, "version": moduleVersion, "showMessage": showMessage } }); 複製代碼
咱們經過 define
方法定義一個 js 模塊,並經過 return
對外暴露出接口(兩個屬性,一個方法)。同時該模塊也是依賴於 jQuery。
RequireJS 支持使用 require.config
來配置項目,具體 API 使用方法見官網文檔或網上資料,這裏只作基本介紹。
在前端的模塊化發展上,還有另外一種與 AMD 相提並論的規範,這就是 CMD:
CMD 即 Common Module Definition 通用模塊定義。 CMD 是 SeaJS 在推廣過程當中對模塊定義的規範化產出。 CMD 規範的前身是 Modules/Wrappings 規範。
在 CMD 規範中,一個模塊就是一個文件。代碼的書寫格式以下:
define(factory);
複製代碼
Function
define 是一個全局函數,用來定義模塊。
define(factory)
define
接受 factory
參數,factory
能夠是一個函數,也能夠是一個對象或字符串。
factory
爲對象、字符串時,表示模塊的接口就是該對象、字符串。好比能夠以下定義一個 JSON 數據模塊:
define({ "foo": "bar" }); 複製代碼
也能夠經過字符串定義模板模塊:
define('I am a template. My name is {{name}}.'); 複製代碼
factory
爲函數時,表示是模塊的構造方法。執行該構造方法,能夠獲得模塊向外提供的接口。factory
方法在執行時,默認會傳入三個參數:require
、exports
和 module
:
define(function(require, exports, module) { // 模塊代碼 }); 複製代碼
define(id?, deps?, factory)
define
也能夠接受兩個以上參數。字符串 id
表示模塊標識,數組 deps
是模塊依賴。好比:
define('hello', ['jquery'], function(require, exports, module) { // 模塊代碼 }); 複製代碼
id
和 deps
參數能夠省略。省略時,能夠經過構建工具自動生成。
注意:帶 id
和 deps 參數的 define
用法不屬於 CMD 規範,而屬於 Modules/Transport 規範。
define.cmd
一個空對象,可用來斷定當前頁面是否有 CMD 模塊加載器:
if (typeof define === "function" && define.cmd) { // 有 Sea.js 等 CMD 模塊加載器存在 } 複製代碼
Function
require
是 factory
函數的第一個參數。
require(id)
require
是一個方法,接受模塊標識做爲惟一參數,用來獲取其餘模塊提供的接口。
define(function(require, exports) { // 獲取模塊 a 的接口 var a = require('./a'); // 調用模塊 a 的方法 a.doSomething(); }); 複製代碼
require.async(id, callback?)
require.async
方法用來在模塊內部異步加載模塊,並在加載完成後執行指定回調。callback
參數可選。
define(function(require, exports, module) { // 異步加載一個模塊,在加載完成時,執行回調 require.async('./b', function(b) { b.doSomething(); }); // 異步加載多個模塊,在加載完成時,執行回調 require.async(['./c', './d'], function(c, d) { c.doSomething(); d.doSomething(); }); }); 複製代碼
注意:require
是同步往下執行,require.async
則是異步回調執行。require.async
通常用來加載可延遲異步加載的模塊。
require.resolve(id)
使用模塊系統內部的路徑解析機制來解析並返回模塊路徑。該函數不會加載模塊,只返回解析後的絕對路徑。
define(function(require, exports) { console.log(require.resolve('./b')); // ==> http://example.com/path/to/b.js }); 複製代碼
這能夠用來獲取模塊路徑,通常用在插件環境或需動態拼接模塊路徑的場景下。
Object
exports
是一個對象,用來向外提供模塊接口。
define(function(require, exports) { // 對外提供 foo 屬性 exports.foo = 'bar'; // 對外提供 doSomething 方法 exports.doSomething = function() {}; }); 複製代碼
除了給 exports
對象增長成員,還可使用 return
直接向外提供接口。
define(function(require) { // 經過 return 直接提供接口 return { foo: 'bar', doSomething: function() {} }; }); 複製代碼
若是 return
語句是模塊中的惟一代碼,還可簡化爲:
define({ foo: 'bar', doSomething: function() {} }); 複製代碼
特別注意
:下面這種寫法是錯誤的!
define(function(require, exports) { // 錯誤用法!!! exports = { foo: 'bar', doSomething: function() {} }; }); 複製代碼
正確的寫法是用 return
或者給 module.exports
賦值:
define(function(require, exports, module) { // 正確寫法 module.exports = { foo: 'bar', doSomething: function() {} }; }); 複製代碼
提示:exports
僅僅是 module.exports
的一個引用。在 factory
內部給 exports
從新賦值時,並不會改變 module.exports
的值。所以給 exports
賦值是無效的,不能用來更改模塊接口。
Object
module
是一個對象,上面存儲了與當前模塊相關聯的一些屬性和方法。
module.id String
模塊的惟一標識。
define('id', [], function(require, exports, module) { // 模塊代碼 }); 複製代碼
上面代碼中,define
的第一個參數就是模塊標識。
module.uri String
根據模塊系統的路徑解析規則獲得的模塊絕對路徑。
define(function(require, exports, module) { console.log(module.uri); // ==> http://example.com/path/to/this/file.js }); 複製代碼
通常狀況下(沒有在 define
中手寫 id
參數時),module.id
的值就是 module.uri
,二者徹底相同。
module.dependencies Array
dependencies
是一個數組,表示當前模塊的依賴。
module.exports Object
當前模塊對外提供的接口。
傳給 factory
構造方法的 exports
參數是 module.exports
對象的一個引用。只經過 exports
參數來提供接口,有時沒法知足開發者的全部需求。 好比當模塊的接口是某個類的實例時,須要經過 module.exports
來實現:
define(function(require, exports, module) { // exports 是 module.exports 的一個引用 console.log(module.exports === exports); // true // 從新給 module.exports 賦值 module.exports = new SomeClass(); // exports 再也不等於 module.exports console.log(module.exports === exports); // false }); 複製代碼
注意:對 module.exports
的賦值須要同步執行,不能放在回調函數裏。下面這樣是不行的:
// x.js define(function(require, exports, module) { // 錯誤用法 setTimeout(function() { module.exports = { a: "hello" }; }, 0); }); 複製代碼
文檔地址:Sea.js - A Module Loader for the Web
簡單入手:
<!DOCTYPE html> <html> <head> <script type="text/javascript" src="sea.js"></script> <script type="text/javascript"> // seajs 的簡單配置 seajs.config({ base: "../sea-modules/", alias: { "jquery": "jquery/jquery/1.10.1/jquery.js" } }) // 加載入口模塊 seajs.use("../static/hello/src/main") </script> </head> <body> </body> </html> 複製代碼
// 全部模塊都經過 define 來定義 define(function(require, exports, module) { // 經過 require 引入依賴 var $ = require('jquery'); var Spinning = require('./spinning'); // 經過 exports 對外提供接口 exports.doSomething = ... // 或者經過 module.exports 提供整個接口 module.exports = ... }); 複製代碼
特色:兼容 AMD 和 CommonJS 規範的同時,還兼容全局引用的方式
常規寫法:
(function (root, factory) { if (typeof define === 'function' && define.amd) { //AMD define(['jquery'], factory); } else if (typeof exports === 'object') { //Node, CommonJS之類的 module.exports = factory(require('jquery')); } else { //瀏覽器全局變量(root 即 window) root.returnExports = factory(root.jQuery); } }(this, function ($) { //方法 function myFunc(){}; //暴露公共方法 return myFunc; })); 複製代碼
在 ES Module 以前,社區制定了一些模塊加載方案,最主要的有 CommonJS 和 AMD 兩種。前者用於服務器,後者用於瀏覽器。ES Module 在語言標準的層面上,實現了模塊功能,並且實現得至關簡單,徹底能夠取代 CommonJS 和 AMD 規範,成爲瀏覽器和服務器通用的模塊解決方案。
ES Module 的設計思想是儘可能的靜態化,使得編譯時就能肯定模塊的依賴關係,以及輸入和輸出的變量。CommonJS 和 AMD 模塊,都只能在運行時肯定這些東西。
CommonJS 和 AMD 模塊,其本質是在運行時生成一個對象進行導出,稱爲「運行時加載」,無法進行「編譯優化」,而 ES Module 不是對象,而是經過 export
命令顯式指定輸出的代碼,再經過 import
命令輸入。這稱爲「編譯時加載」或者靜態加載,即 ES Module 能夠在編譯時就完成模塊加載,效率要比 CommonJS 模塊的加載方式高。固然,這也致使了無法引用 ES Module 模塊自己,由於它不是對象。
因爲 ES Module 是編譯時加載,使得靜態分析成爲可能。有了它,就能進一步拓寬 JavaScript 的語法,好比引入宏(macro)和類型檢驗(type system)這些只能靠靜態分析實現的功能。
除了靜態加載帶來的各類好處,ES Module 還有如下好處:
import
只能寫在頂層,由於是靜態語法export
只支持導出接口,能夠看做對象形式,值沒法被當成接口,因此是錯誤的。/*錯誤的寫法*/ // 寫法一 export 1; // 寫法二 var m = 1; export m; /*正確的四種寫法*/ // 寫法一 export var m = 1; // 寫法二 var m = 1; export {m}; // 寫法三 var n = 1; export {n as m}; // 寫法四 var n = 1; export default n; 複製代碼
export default
命令用於指定模塊的默認輸出。export default
就是輸出一個叫作 default
的變量或方法,而後系統容許你爲它取任意名字// modules.js function add(x, y) { return x * y; } export {add as default}; // 等同於 // export default add; // app.js import { default as foo } from 'modules'; // 等同於 // import foo from 'modules'; 複製代碼
JavaScript 模塊規範主要有四種:CommonJS、AMD、CMD、ES Module。 CommonJS 用在服務器端,AMD 和CMD 用在瀏覽器環境,ES Module 是做爲終極通用解決方案。