## 前言
The Module Pattern,模塊模式燎竖,也譯為模組模式肆氓,是一種通用的對(duì)代碼進(jìn)行模塊化組織與定義的方式。這里所說(shuō)的模塊(Modules)底瓣,是指實(shí)現(xiàn)某特定功能的一組方法和代碼。許多現(xiàn)代語(yǔ)言都定義了代碼的模塊化組織方式蕉陋,比如 Golang 和 Java捐凭,它們都使用 package 與 import 來(lái)管理與使用模塊,而目前版本的 JavaScript 并未提供一種原生的凳鬓、語(yǔ)言級(jí)別的模塊化組織模式茁肠,而是將模塊化的方法交由開(kāi)發(fā)者來(lái)實(shí)現(xiàn)。因此缩举,出現(xiàn)了很多種 JavaScript 模塊化的實(shí)現(xiàn)方式垦梆,比如匹颤,CommonJS Modules、AMD 等托猩。
以 AMD 為例印蓖,該規(guī)范使用 define 函數(shù)來(lái)定義模塊。使用 AMD 規(guī)范進(jìn)行模塊化編程是很簡(jiǎn)單的京腥,大致上的結(jié)構(gòu)是這樣的:
define(factory(){
// 模塊代碼
// return something;
});
目前尚在制定中的 Harmony/ECMAScript 6(也稱(chēng)為 ES.next)赦肃,會(huì)對(duì)模塊作出語(yǔ)言級(jí)別的定義,但距離實(shí)用尚遙不可及公浪,這里暫時(shí)不討論它他宛。
作為一種模式,模塊模式其實(shí)一直伴隨著 JavaScript 存在欠气,與 ES 6 無(wú)關(guān)厅各。最近我需要重構(gòu)自己的一些代碼,因此我參考和總結(jié)了一些實(shí)用的模塊化編程實(shí)踐预柒,以便更好的組織我的代碼队塘。需要注意的是,本文只是個(gè)人的一個(gè)總結(jié)卫旱,比較簡(jiǎn)單和片面人灼,詳盡的內(nèi)容與剖析請(qǐng)參看文后的參考資料,它們寫(xiě)得很好顾翼。本文并不關(guān)心模塊如何載入投放,只關(guān)心現(xiàn)今該如何組織模塊化的代碼。還有适贸,不必過(guò)于糾結(jié)所謂的模式灸芳,真正重要的其實(shí)還是模塊代碼及思想。所謂模式拜姿,不過(guò)是我們書(shū)寫(xiě)代碼的一些技巧和經(jīng)驗(yàn)的總結(jié)烙样,是一些慣用法,實(shí)踐中應(yīng)靈活運(yùn)用蕊肥。
## 模塊模式
### 閉包與 IIFE (Immediately-Invoked Function Expression)
模塊模式使用了 JavaScript 的一個(gè)特性谒获,即閉包(Closures)。現(xiàn)今流行的一些 JS 庫(kù)中經(jīng)常見(jiàn)到以下形式的代碼:
;(function(參數(shù)) {
// 模塊代碼
// return something;
})(參數(shù));
上面的代碼定義了一個(gè)匿名函數(shù)壁却,并立即調(diào)用自己批狱,這叫做自調(diào)用匿名函數(shù)(SIAF),更準(zhǔn)確一點(diǎn)展东,稱(chēng)為立即調(diào)用的函數(shù)表達(dá) (Immediately-Invoked Function Expression, IIFE–讀做“iffy”)赔硫。
在閉包中,可以定義私有變量和函數(shù)盐肃,外部無(wú)法訪(fǎng)問(wèn)它們爪膊,從而做到了私有成員的隱藏和隔離权悟。而通過(guò)返回對(duì)象或函數(shù),或是將某對(duì)象作為參數(shù)傳入推盛,在函數(shù)體內(nèi)對(duì)該對(duì)象進(jìn)行操作峦阁,就可以公開(kāi)我們所希望對(duì)外暴露的公開(kāi)的方法與數(shù)據(jù)。
這小槐,其實(shí)就是模塊模式的本質(zhì)拇派。
注1:上面的代碼中,最后的一對(duì)括號(hào)是對(duì)匿名函數(shù)的調(diào)用凿跳,因此必不可少件豌。而前面的一對(duì)圍繞著函數(shù)表達(dá)式的一對(duì)括號(hào)并不是必需的,但它可以用來(lái)給開(kāi)發(fā)人員一個(gè)指示 -- 這是一個(gè) IIFE控嗜。也有一些開(kāi)發(fā)者在函數(shù)表達(dá)式前面加上一個(gè)驚嘆號(hào)(!)或分號(hào)(;)茧彤,而不是用括號(hào)包起來(lái)。比如 knockoutjs 的源碼大致就是這樣的:
!function(參數(shù)) {
// 代碼
// return something
}(參數(shù));
還有些人喜歡用括號(hào)將整個(gè) IIFE 圍起來(lái)疆栏,這樣就變成了以下的形式:
(function(參數(shù)) {
// 代碼
// return something
}(參數(shù)));
注2:在有些人的代碼中曾掂,將 undefined 作為上面代碼中的一個(gè)參數(shù),他們那樣做是因?yàn)?undefined 并不是 JavaScript 的保留字壁顶,用戶(hù)也可以定義它珠洗,這樣,當(dāng)判斷某個(gè)值是否是 undefined 的時(shí)候若专,判斷可能會(huì)是錯(cuò)誤的许蓖。將 undefined 作為一個(gè)參數(shù)傳入,是希望代碼能按預(yù)期那樣運(yùn)行调衰。不過(guò)我認(rèn)為膊爪,一般情況下那樣做并沒(méi)太大意義。
### 參數(shù)輸入
JavaScript 有一個(gè)特性叫做隱式全局變量(implied globals)嚎莉,當(dāng)使用一個(gè)變量名時(shí)米酬,JavaScript 解釋器將反向遍歷作用域鏈來(lái)查找變量的聲明,如果沒(méi)有找到趋箩,就假定該變量是全局變量赃额。這種特性使得我們可以在閉包里隨處引用全局變量,比如 jQuery 或 window叫确。然而跳芳,這是一種不好的方式。
考慮模塊的獨(dú)立性和封裝启妹,對(duì)其它對(duì)象的引用應(yīng)該通過(guò)參數(shù)來(lái)引入。如果模塊內(nèi)需要使用其它全局對(duì)象醉旦,應(yīng)該將這些對(duì)象作為參數(shù)來(lái)顯式引用它們饶米,而非在模塊內(nèi)直接引用這些對(duì)象的名字桨啃。以 jQuery 為例,若在參數(shù)中沒(méi)有輸入 jQuery 對(duì)象就在模塊內(nèi)直接引用 $ 這個(gè)對(duì)象檬输,是有出錯(cuò)的可能的照瘾。正確的方式大致應(yīng)該是這樣的:
;(function(q, w) {
// q is jQuery
// w is window
// 局部變量及代碼
// 返回
})(jQuery, window);
相比隱式全局變量,將引用的對(duì)象作為參數(shù)丧慈,使它們得以和函數(shù)內(nèi)的其它局部變量區(qū)分開(kāi)來(lái)析命。這樣做還有個(gè)好處,我們可以給那些全局對(duì)象起一個(gè)別名逃默,比如上例中的 "q"【榉撸現(xiàn)在看看你的代碼,是否沒(méi)有經(jīng)過(guò)對(duì) jQuery 的引用就到處都是"$"完域?
### 模塊輸出(Module Export)
有時(shí)我們不只是要使用全局變量软吐,我們也要聲明和輸出模塊中的對(duì)象,這可以通過(guò)匿名函數(shù)的 return 語(yǔ)句來(lái)達(dá)成吟税,而這也構(gòu)成了一個(gè)完整的模塊模式凹耙。來(lái)看一個(gè)完整的例子:
varMODULE = (function() {
varmy = {},
privateVariable = 1;
functionprivateMethod() {
// ...
}
my.moduleProperty = 1;
my.moduleMethod =function() {
// ...
};
returnmy;
}());
這段代碼聲明了一個(gè)變量 MODULE,它帶有兩個(gè)可訪(fǎng)問(wèn)的屬性:moduleProperty 和 moduleMethod肠仪,其它的代碼都封裝在閉包中保持著私有狀態(tài)肖抱。參考以前提過(guò)的參數(shù)輸入,我們還可以通過(guò)參數(shù)引用其它全局變量异旧。
#### 輸出簡(jiǎn)單對(duì)象
很多時(shí)候我們 return 一個(gè)對(duì)象作為模塊的輸出意述,比如上例就是。
另外泽艘,使用對(duì)象直接量(Object Literal Notation)來(lái)表達(dá) JavaScript 對(duì)象是很常見(jiàn)的欲险。比如:var x = { p1: 1, p2: "2", f: function(){ /*... */ } }
很多時(shí)候我們都能見(jiàn)到這樣的模塊化代碼:
varModule1 = (function() {
varprivate_variable = 1;
functionprivate_method() {/*...*/}
varmy = {
property1: 1,
property2: private_variable,
method1: private_method,
method2:function() {
// ...
}
};
returnmy;
}());
另外,對(duì)于簡(jiǎn)單的模塊化代碼匹涮,若不涉及私有成員等天试,其實(shí)也可以直接使用對(duì)象直接量來(lái)表達(dá)一個(gè)模塊:
varWidget1 = {
name:"who am i?",
settings: {
x: 0,
y: 0
},
call_me:function() {
// ...
}
};
有一篇文章講解了這種形式:How Do You Structure JavaScript? The Module Pattern Edition
不過(guò)這只是一種簡(jiǎn)單的形式,你可以將它看作是模塊模式的一種基礎(chǔ)的簡(jiǎn)單表達(dá)形式然低,而把閉包形式看作是對(duì)它的一個(gè)封裝喜每。
#### 輸出函數(shù)
有時(shí)候我們希望返回的并不是一個(gè)對(duì)象,而是一個(gè)函數(shù)雳攘。有兩種需求要求我們返回一個(gè)函數(shù)带兜,一種情況是我們需要它是一個(gè)函數(shù),比如 jQuery吨灭,它是一個(gè)函數(shù)而不是一個(gè)簡(jiǎn)單對(duì)象刚照;另一種情況是我們需要的是一個(gè)“類(lèi)”而不是一個(gè)直接量,之后我們可以用 "new" 來(lái)實(shí)例它喧兄。目前版本的 JavaScript 并沒(méi)有專(zhuān)門(mén)的“類(lèi)”定義无畔,但它卻可以通過(guò) function 來(lái)表達(dá)啊楚。
varCat = (function() {
// 私有成員及代碼 ...
returnfunction(name) {
this.name = name;
this.bark =function() {/*...*/}
};
}());
vartomcat =newCat("Tom");
tomcat.bark();
為什么不直接定義一個(gè) function 而要把它放在閉包里呢?簡(jiǎn)單點(diǎn)的情況浑彰,確實(shí)不需要使用 IIFE 這種形式恭理,但復(fù)雜點(diǎn)的情況,在構(gòu)造我們所需要的函數(shù)或是“類(lèi)”時(shí)郭变,若需要定義一些私有的函數(shù)颜价,就有必要使用 IIFE 這種形式了。
另外诉濒,在 ECMAScript 第五版中周伦,提出了 Object.create() 方法。這時(shí)可以將一個(gè)對(duì)象視作“類(lèi)”循诉,并使用 Object.create() 進(jìn)行實(shí)例化横辆,不需使用 "new"。
### Revealing Module Pattern
前面已經(jīng)提到一種形式是輸出對(duì)象直接量(Object Literal Notation)茄猫,而 Revealing Module Pattern 其實(shí)就是這種形式狈蚤,只是做了一些限定。這種模式要求在私有范圍內(nèi)中定義變量和函數(shù)划纽,然后返回一個(gè)匿名對(duì)象脆侮,在該對(duì)象中指定要公開(kāi)的成員。參見(jiàn)下面的代碼:
varMODULE = (function() {
// 私有變量及函數(shù)
varx = 1;
functionf1() {}
functionf2() {}
return{
public_method1: f1,
public_method2: f2
};
}());
## 模塊模式的變化
### 擴(kuò)展
上面的舉例都是在一個(gè)地方定義模塊勇劣,如果我們需要在數(shù)個(gè)文件中分別編寫(xiě)一個(gè)模塊的不同部分該怎么辦呢靖避?或者說(shuō),如果我們需要對(duì)已有的模塊作出擴(kuò)展該怎么辦呢比默?其實(shí)也很簡(jiǎn)單幻捏,將模塊對(duì)象作為參數(shù)輸入,擴(kuò)展后再返回自己就可以了命咐。比如:
varMODULE = (function(my) {
my.anotherMethod =function() {
// added method...
};
returnmy;
}(MODULE));
上面的代碼為對(duì)象 MODULE 增加了一個(gè) "anotherMethod" 方法篡九。
### 松耦合擴(kuò)展(Loose Augmentation)
上面的代碼要求 MODULE 對(duì)象是已經(jīng)定義過(guò)的。如果這個(gè)模塊的各個(gè)組成部分并沒(méi)有加載順序要求的話(huà)醋奠,其實(shí)可以允許輸入的參數(shù)為空對(duì)象榛臼,那么我們將上例中的參數(shù)由 MODULE 改為 MODULE || {} 就可以了:
varMODULE = (function(my) {
// add capabilities...
returnmy;
}(MODULE || {}));
### 緊耦合擴(kuò)展(Tight Augmentation)
與上例不同,有時(shí)我們要求在擴(kuò)展時(shí)調(diào)用以前已被定義的方法窜司,這也有可能被用于覆蓋已有的方法沛善。這時(shí),對(duì)模塊的定義順序是有要求的塞祈。
varMODULE = (function(my) {
varold_moduleMethod = my.moduleMethod;
my.moduleMethod =function() {
// 方法重載
// 可通過(guò) old_moduleMethod 調(diào)用以前的方法...
};
returnmy;
}(MODULE));
### 克隆與繼承(Cloning and Inheritance)
varMODULE_TWO = (function(old) {
varmy = {},
key;
for(keyinold) {
if(old.hasOwnProperty(key)) {
my[key] = old[key];
}
}
varsuper_moduleMethod = old.moduleMethod;
my.moduleMethod =function() {
// override method on the clone, access to super through super_moduleMethod
};
returnmy;
}(MODULE));
有時(shí)我們需要復(fù)制和繼承原對(duì)象金刁,上面的代碼演示了這種操作,但未必完美。如果你可以使用 Object.create() 的話(huà)尤蛮,請(qǐng)使用 Object.create() 來(lái)改寫(xiě)上面的代碼:
varMODULE_TWO = (function(old) {
varmy = Object.create(old);
varsuper_moduleMethod = old.moduleMethod;
my.moduleMethod =function() {
// override method ...
};
returnmy;
}(MODULE));
### 子模塊(Sub-modules)
模塊對(duì)象當(dāng)然可以再包含子模塊漠秋,形如 MODULE.Sub=(function(){}()) 之類(lèi),這里不再展開(kāi)敘述了抵屿。
### 各種形式的混合
以上介紹了常見(jiàn)的幾種模塊化形式,實(shí)際應(yīng)用中有可能是這些形式的混合體捅位。比如:
varUTIL = (function(parent, $) {
varmy = parent.ajax = parent.ajax || {};
my.get =function(url, params, callback) {
// ok, so I'm cheating a bit :)
return$.getJSON(url, params, callback);
};
// etc...
returnparent;
}(UTIL || {}, jQuery));
## 與其它模塊規(guī)范或 JS 庫(kù)的適配
### 模塊環(huán)境探測(cè)
現(xiàn)今轧葛,CommonJS Modules 與 AMD 有著廣泛的應(yīng)用,如果確定 AMD 的 define 是可用的艇搀,我們當(dāng)然可以使用 define 來(lái)編寫(xiě)模塊化的代碼尿扯。然而,我們不能假定我們的代碼必然運(yùn)行于 AMD 環(huán)境下焰雕。有沒(méi)有辦法可以讓我們的代碼既兼容于 CommonJS Modules 或 AMD 規(guī)范衷笋,又能在一般環(huán)境下運(yùn)行呢?
其實(shí)我們只需要在某個(gè)地方加上對(duì) CommonJS Modules 與 AMD 的探測(cè)并根據(jù)探測(cè)結(jié)果來(lái)“注冊(cè)”自己就可以了矩屁,以上那些模塊模式仍然有用辟宗。
AMD 定義了 define 函數(shù),我們可以使用 typeof 探測(cè)該函數(shù)是否已定義吝秕。若要更嚴(yán)格一點(diǎn)泊脐,可以繼續(xù)判斷 define.amd 是否有定義。另外烁峭,SeaJS 也使用了 define 函數(shù)容客,但和 AMD 的 define 又不太一樣。
對(duì)于 CommonJS约郁,可以檢查 exports 或是 module.exports 是否有定義缩挑。
現(xiàn)在,我寫(xiě)一個(gè)比較直白的例子來(lái)展示這個(gè)過(guò)程:
varMODULE = (function() {
varmy = {};
// 代碼 ...
if(typeofdefine =='function') {
define(function(){returnmy; } );
}elseif(typeofmodule !='undefined'&& module.exports) {
module.exports = my;
}
returnmy;
}());
上面的代碼在返回 my 對(duì)象之前鬓梅,先檢測(cè)自己是否是運(yùn)行在 AMD 環(huán)境之中(檢測(cè) define 函數(shù)是否有定義)供置,如果是,就使用 define 來(lái)定義模塊己肮,否則士袄,繼續(xù)檢測(cè)是否運(yùn)行于 CommonJS 中,比如 NodeJS谎僻,如果是娄柳,則將 my 賦值給 module.exports。因此艘绍,這段代碼應(yīng)該可以同時(shí)運(yùn)行于 AMD赤拒、CommonJS 以及一般的環(huán)境之中。另外,我們的這種寫(xiě)法應(yīng)該也可在 SeaJS 中正確執(zhí)行挎挖。
### 其它一些 JS 庫(kù)的做法
現(xiàn)在許多 JS 庫(kù)都加入了對(duì) AMD 或 CommonJS Modules 的適應(yīng)这敬,比如 jQuery, Mustache, doT, Juicer 等。
jQuery 的寫(xiě)法可參考 exports.js:
if(typeofmodule ==="object"&& module &&typeofmodule.exports ==="object") {
module.exports = jQuery;
}else{
if(typeofdefine ==="function"&& define.amd ) {
define("jquery", [],function() {returnjQuery; } );
}
}
if(typeofwindow ==="object"&&typeofwindow.document ==="object") {
window.jQuery = window.$ = jQuery;
}
與前面我寫(xiě)的那段代碼有些不同蕉朵,在對(duì) AMD 和 CommonJS 探測(cè)之后崔涂,它將 jQuery 注冊(cè)成了 window 對(duì)象的成員。
然而始衅,jQuery 是一個(gè)瀏覽器端的 JS 庫(kù)冷蚂,它那樣寫(xiě)當(dāng)然沒(méi)問(wèn)題。但如果我們所寫(xiě)的是一個(gè)通用的庫(kù)汛闸,就不應(yīng)使用 window 對(duì)象了蝙茶,而應(yīng)該使用全局對(duì)象,而這一般可以使用 this 來(lái)得到诸老。
我們看看 Mustache 是怎么做的:
(function(root, factory) {
if(typeofexports ==="object"&& exports) {
factory(exports);// CommonJS
}else{
varmustache = {};
factory(mustache);
if(typeofdefine ==="function"&& define.amd) {
define(mustache);// AMD
}else{
root.Mustache = mustache;//
}
}
}(this,function(mustache) {
// 模塊主要的代碼放在這兒
});
這段代碼與前面介紹的方式不太一樣隆夯,它使用了兩個(gè)匿名函數(shù)。后面那個(gè)函數(shù)可以看作是模塊代碼的工廠(chǎng)函數(shù)别伏,它是模塊的主體部分蹄衷。前面那個(gè)函數(shù)對(duì)運(yùn)行環(huán)境進(jìn)行檢測(cè),根據(jù)檢測(cè)的結(jié)果對(duì)模塊的工廠(chǎng)函數(shù)進(jìn)行調(diào)用厘肮。另外宦芦,作為一個(gè)通用庫(kù),它并沒(méi)使用 window 對(duì)象轴脐,而是使用了 this调卑,因?yàn)樵诤?jiǎn)單的函數(shù)調(diào)用中,this 其實(shí)就是全局對(duì)象大咱。
再看看 doT 的做法僚匆。doT 的做法與 Mustache 不同隙轻,而是更接近于我在前面介紹 AMD 環(huán)境探測(cè)的那段代碼:
(function() {
"use strict";
vardoT = {
version:'1.0.0',
templateSettings: {/*...*/},
template: undefined,//fn, compile template
compile:? undefined//fn, for express
};
if(typeofmodule !=='undefined'&& module.exports) {
module.exports = doT;
}elseif(typeofdefine ==='function'&& define.amd) {
define(function(){returndoT;});
}else{
(function(){returnthis|| (0,eval)('this'); }()).doT = doT;
}
// ...
}());
這段代碼里的 (0, eval)('this') 是一個(gè)小技巧,這個(gè)表達(dá)式用來(lái)得到 Global 對(duì)象,'this' 其實(shí)是傳遞給 eval 的參數(shù)湖饱,但由于 eval 是經(jīng)由 (0, eval) 這個(gè)表達(dá)式間接得到的蜘矢,因此 eval 將會(huì)在全局對(duì)象作用域中查找 this揍障,結(jié)果得到的是全局對(duì)象溉旋。若是代碼運(yùn)行于瀏覽器中,那么得到的其實(shí)是 window 對(duì)象煮仇。這里有一個(gè)針對(duì)它的討論:http://stackoverflow.com/questions/14119988/return-this-0-evalthis/14120023#14120023
其實(shí)也有其它辦法來(lái)獲取全局對(duì)象的劳跃,比如,使用函數(shù)的 call 或 apply浙垫,但不給參數(shù)刨仑,或是傳入 null:
varglobal_object = (function(){returnthis; }).call();
你可以參考這篇文章:Javascript的this用法
Juicer 則沒(méi)有檢測(cè) AMD郑诺,它使用了如下的語(yǔ)句來(lái)檢測(cè) CommonJS Modules:
typeof(module) !=='undefined'&& module.exports ? module.exports = juicer :this.juicer = juicer;
另外,你還可以參考一下這個(gè):https://gist.github.com/kitcambridge/1251221
(function(root, Library) {
// The square bracket notation is used to avoid property munging by the Closure Compiler.
if(typeofdefine =="function"&&typeofdefine["amd"] =="object"&& define["amd"]) {
// Export for asynchronous module loaders (e.g., RequireJS, `curl.js`).
define(["exports"], Library);
}else{
// Export for CommonJS environments, web browsers, and JavaScript engines.
Library = Library(typeofexports =="object"&& exports || (root["Library"] = {
"noConflict": (function(original) {
functionnoConflict() {
root["Library"] = original;
// `noConflict` can't be invoked more than once.
deleteLibrary.noConflict;
returnLibrary;
}
returnnoConflict;
})(root["Library"])
}));
}
})(this,function(exports) {
// ...
returnexports;
});
我覺(jué)得這個(gè)寫(xiě)得有些復(fù)雜了杉武,我也未必需要我的庫(kù)帶有 noConflict 方法辙诞。不過(guò),它也可以是個(gè)不錯(cuò)的參考轻抱。
## JavaScript 模塊化的未來(lái)
未來(lái)的模塊化方案會(huì)是什么樣的飞涂?我不知道,但不管將來(lái)如何演化祈搜,作為一種模式封拧,模塊模式是不會(huì)過(guò)時(shí)和消失的。
如前所述夭问,尚在制定中的 ES 6 會(huì)對(duì)模塊作出語(yǔ)言級(jí)別的定義。我們來(lái)看一個(gè)實(shí)例曹铃,以下的代碼段摘自“ES6:JavaScript中將會(huì)有的幾個(gè)新東西”:
module Car {
// 內(nèi)部變量
varlicensePlateNo ='556-343';
// 暴露到外部的變量和函數(shù)
exportfunctiondrive(speed, direction) {
console.log('details:', speed, direction);
}
exportmodule engine{
exportfunctioncheck() { }
}
exportvarmiles = 5000;
exportvarcolor ='silver';
};
我不知道 ES 6 將來(lái)會(huì)否對(duì)此作出改變缰趋,對(duì)上面的這種代碼形式,不同的人會(huì)有不同的看法陕见。就我個(gè)人而言秘血,我十分不喜歡這種形式!
確實(shí)评甜,我們可能需要有一種統(tǒng)一的模塊化定義方式灰粮。發(fā)明 AMD 和 RequireJS 的人也說(shuō)過(guò) AMD 和 RequireJS 應(yīng)該被淘汰了,運(yùn)行環(huán)境應(yīng)該提供模塊的原生支持忍坷。然而粘舟,ES 6 中的模塊定義是否是正確的?它是否是一個(gè)好的解決方案呢佩研?我不知道柑肴,但我個(gè)人真的很不喜歡那種方式。很多人十分喜歡把其它語(yǔ)言的一些東西生搬硬套到 JavaScript 中旬薯,或是孜孜不倦地要把 JavaScript 變成另外一種語(yǔ)言晰骑,我相當(dāng)討厭這種行為。我并非一個(gè)保守的人绊序,我樂(lè)意接受新概念硕舆、新語(yǔ)法,只要它是好的骤公。但是抚官,ES 6 草案中的模塊規(guī)范是我不喜歡的,起碼阶捆,我認(rèn)為它脫離了現(xiàn)實(shí)耗式,否定了開(kāi)源社區(qū)的實(shí)踐和經(jīng)驗(yàn),是一種意淫出來(lái)的東西,這使得它在目前不能解決任何實(shí)際問(wèn)題刊咳,反而是來(lái)添亂的彪见。
按目前的 ES6 草案所給出的模塊化規(guī)范,它并沒(méi)有采用既有的 CommonJS Modules 和 AMD 規(guī)范娱挨,而是定義了一種新的規(guī)范余指,而且這種規(guī)范修改了 JavaScript 既有的語(yǔ)法形式,使得它沒(méi)有辦法像 ES5 中的 Object.create跷坝、Array.forEach 那樣可以利用現(xiàn)有版本的 JavaScript 編寫(xiě)一些代碼來(lái)實(shí)現(xiàn)它酵镜。這也使得 ES 6 的模塊化語(yǔ)法將在一段時(shí)期內(nèi)處于不可用的狀態(tài)。
引入新的語(yǔ)法也不算是問(wèn)題柴钻,然而淮韭,為了模塊而大費(fèi)周折引出那么多新的語(yǔ)法和定義,真的是一種好的選擇么贴届?話(huà)說(shuō)靠粪,它解決了什么實(shí)質(zhì)性的問(wèn)題而非如此不可?現(xiàn)今流行的 AMD 其實(shí)簡(jiǎn)單到只定義了一個(gè) "define" 函數(shù)毫蚓,它有什么重大問(wèn)題占键?就算那些專(zhuān)家因種種原因或目的而無(wú)法接受 AMD 或其它開(kāi)源社區(qū)的方案,稍作出一些修改和中和總是可以的吧元潘,非要把 JavaScript 改頭換面不可么畔乙?確實(shí)有人寫(xiě)了一些觀(guān)點(diǎn)來(lái)解釋為何不用 AMD,然而翩概,那些解釋和觀(guān)點(diǎn)其實(shí)大都站不住腳牲距。比如說(shuō),其中一個(gè)解釋是 AMD 規(guī)范不兼容于 ES 6钥庇!可笑不可笑嗅虏?ES 6 尚未正式推出,完全實(shí)現(xiàn)了 ES 6 的 JavaScript 運(yùn)行時(shí)也沒(méi)幾個(gè)上沐,而 AMD 在開(kāi)源社區(qū)中早已十分流行皮服,這個(gè)時(shí)候說(shuō) AMD 不兼容 ES 6,我不知道這是什么意思参咙。
就我看來(lái)龄广,現(xiàn)今各種形形色色的所謂標(biāo)準(zhǔn)化工作組,很多時(shí)候像是高高在上的神仙蕴侧,他們拉不下臉全身心地參與到開(kāi)源社區(qū)之中择同,他們就是要作出與開(kāi)源社區(qū)不同的規(guī)范,以此來(lái)彰顯他們的工作净宵、專(zhuān)業(yè)與權(quán)威敲才。而且裹纳,很多時(shí)候他們過(guò)于官僚,又或者夾雜在各大商業(yè)集團(tuán)之間舉棋不定紧武。我不否認(rèn)他們工作的重要性剃氧,然而,以專(zhuān)家自居而脫離或否定開(kāi)源社區(qū)的實(shí)踐阻星,以及商業(yè)與政治的利益均衡等朋鞍,使得他們的工作與開(kāi)源社區(qū)相比,在技術(shù)的推動(dòng)與發(fā)展上成效不足甚至添亂妥箕。
回到 ES 6 中的模塊滥酥,想想看,我需要修改我的代碼畦幢,在其中加上諸如 module, export, import 之類(lèi)的新的語(yǔ)法坎吻,修改之后的代碼卻沒(méi)辦法在現(xiàn)今版本的 JavaScript 中運(yùn)行,而且宇葱,與現(xiàn)今流行的模塊化方案相比瘦真,這些工作也沒(méi)什么實(shí)質(zhì)性的幫助,想想這些贝搁,我只感覺(jué)像是吃了一個(gè)蒼蠅。
ES 6 的發(fā)展當(dāng)然不會(huì)因?yàn)槲业耐锣卸腥魏巫兓科乙膊辉冈僬归_(kāi)討論雷逆。未來(lái)的模塊化方案具體是什么樣的無(wú)法知曉,但起碼我可以得到以下的結(jié)論:
模塊模式不會(huì)過(guò)時(shí)
ES 6 不會(huì)接納 AMD 等現(xiàn)有方案污尉,但不管如何膀哲,JavaScript 將會(huì)有語(yǔ)言級(jí)別的模塊定義
ES 6 中的模塊在一段時(shí)期內(nèi)是不可用的
即使 ES 6 已達(dá)到實(shí)用階段,現(xiàn)今的模塊化方案仍會(huì)存在和發(fā)展
## 參考資料
JavaScript Module Pattern: In-Depth/深入理解JavaScript 模塊模式[譯]
Learning JavaScript Design Patterns
JavaScript Modules/JavaScript模塊化開(kāi)發(fā)一瞥[譯]
JavaScript Closures and the Module Pattern/JavaScript閉包和模塊模式[譯]
(完)
版權(quán)聲明:自由轉(zhuǎn)載-非商用-非衍生-保持署名 |Creative Commons BY-NC-ND 3.0