背景
在ES6以前猾愿,JS語言沒有模塊化,如何讓JS不止運行在瀏覽器德玫,且能更有效的管理代碼匪蟀,于是應(yīng)運而生CommonJS這種規(guī)范,定義了三個全局變量:
require宰僧,exports材彪,module
require
用于引入一個模塊
exports
對外暴露模塊的接口,可以是任何類型
module
是這個模塊本身的對象
用require
引入時獲取的是這個模塊對外暴露的接口(exports
)
Node.js 使用了CommonJS規(guī)范:
var foo = require('foo');
var out = foo.bar();
module.exports = out;
在瀏覽器端琴儿,不像Node.js內(nèi)部支持CommonJS段化,如何進(jìn)行模塊化,
于是出現(xiàn)了 CMD 與 AMD 兩種方式造成,其主要代表是 seajs 和 requirejs显熏,
他們都定義了一個全局函數(shù) define 來創(chuàng)建一個模塊:
// CMD
define(function(require, exports, module) {
var foo = require('foo');
var out = foo.bar();
module.exports = out;
})
// AMD
define(['foo'], function(foo) {
var out = foo.bar();
return out;
});
可以看出CMD完好的保留了CommonJS的風(fēng)格,
而AMD用了一種更簡潔的依賴注入和函數(shù)返回的方式實現(xiàn)模塊化晒屎。
兩者除風(fēng)格不同外最大區(qū)別在于加載依賴模塊的方式喘蟆,
CMD是懶加載,在require時才會加載依賴鼓鲁,
而AMD是預(yù)加載蕴轨,在定義模塊時就提前加載好所有依賴。
各有千秋骇吭,各有適合的場景橙弱,網(wǎng)上有兩者詳細(xì)評測和激烈的討論。
正題
我們要實現(xiàn)一個模塊燥狰,讓它既能在seajs(CMD)環(huán)境里引入棘脐,又能在requirejs(AMD)環(huán)境中引入,
當(dāng)然也能在Node.js(CommonJS)中使用龙致,另外還可以在沒有模塊化的環(huán)境中用script標(biāo)簽全局引入蛀缝,
可謂是對write once,run anywhere的向往目代,實際上大部分npm的前端組件包也要考慮這個屈梁。
- 首先一個模塊看起來應(yīng)該是這樣:
var moduleName = {};
return moduleName;
當(dāng)然,模塊輸出的不止可以是對象像啼,還是可以是任何值,包括一個類潭苞。
- 分析CMD和AMD忽冻,我們需要提供一個工廠函數(shù)傳入define來定義模塊,所以變成這樣:
function factory () {
var moduleName = {};
return moduleName;
}
- 為適應(yīng)Node.js此疹,可以來判斷全局變量僧诚,由于require在CMD和ADM中都有定義遮婶,所以只判斷:
typeof module !== 'undefined' && typeof exports === 'object'
于是變成這樣:
function factory () {
var moduleName = {};
return moduleName;
}
if (typeof module !== 'undefined' && typeof exports === 'object') {
module.exports = factory()
}
至此已經(jīng)能夠滿足Node.js的需求。
- 當(dāng)沒有上述全局變量湖笨,且有define全局變量時旗扑,我們認(rèn)為是AMD或CMD,可以直接將factory傳入define:
function factory () {
var moduleName = {};
return moduleName;
}
if (typeof module !== 'undefined' && typeof exports === 'object') {
module.exports = factory()
} else if (typeof define === 'function' && (define.cmd || define.amd)) {
define(factory)
}
注意:CMD其實也支持return返回模塊接口慈省,所以兩者可以通用臀防。
- 最后是script標(biāo)簽全局引入,我們可以將模塊放在window上边败,
為了模塊內(nèi)部在瀏覽器和Node.js中都能使用全局對象袱衷,我們可以做此判斷:
var global = typeof window !== 'undefined' ? window : global;
同時,我們用一個立刻執(zhí)行的閉包函數(shù)將所有代碼包含笑窜,來避免污染全局空間致燥,
并將global對象傳入閉包函數(shù),最終變成這樣:
;(function (global) {
function factory () {
var moduleName = {};
return moduleName;
}
if (typeof module !== 'undefined' && typeof exports === 'object') {
module.exports = factory()
} else if (typeof define === 'function' && (define.cmd || define.amd)) {
define(factory)
} else {
global.moduleName = factory();
}
})(typeof window !== 'undefined' ? window : global);
注意:閉包前加上分號是為了給前一個模塊填坑排截,分號多了沒問題嫌蚤,少了則語句可能發(fā)生變化。
- 我們參考一下Vuex的源碼:
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.Vuex = factory());
}(this, (function () {
'use strict';
// ……
var index = {
Store: Store,
install: install,
version: '2.5.0',
mapState: mapState,
mapMutations: mapMutations,
mapGetters: mapGetters,
mapActions: mapActions,
createNamespacedHelpers: createNamespacedHelpers
};
return index;
})));
這里有兩個變化:
函數(shù)factory
以匿名函數(shù)的方式引入断傲,結(jié)構(gòu)從;()();
變成 ;(()());
脱吱;
用this
代替了typeof window !== 'undefined' ? window : global
,this
在瀏覽器是window
艳悔,在Node中是golbal
急凰,很是精妙。
- 稍微簡化一下:
;(function (global) {
function factory () {
var index= {};
return index;
}
typeof module !== 'undefined' && typeof exports === 'object' ? module.exports = factory() :
typeof define === 'function' && (define.cmd || define.amd) ? define(factory) :
(global.moduleName = factory());
})(this);
或者
;(function (flobal, factory) {
typeof module !== 'undefined' && typeof exports === 'object' ? module.exports = factory() :
typeof define === 'function' && (define.cmd || define.amd) ? define(factory) :
(global.moduleName = factory());
}(this, (function () {
var index = {};
return index;
}
)));
- 于是同一個js文件我們能愉快的在不同環(huán)境這樣引入:
// Node.js
var myModule = require('moduleName');
// Seajs
define(function (require, exports, module) {
var myModule = require('moduleName');
})
// Requirejs
define(['moduleName'], function (moduleName) {
})
// Browser global
<script src="moduleName.js"></script>
感謝瀏覽猜年,歡迎評論指正抡锈,轉(zhuǎn)載請標(biāo)明出處。
參考博文:http://www.cnblogs.com/brandonchen/p/5550470.html