JavaScript 模塊化編程 - Module Pattern

## 前言

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閉包和模塊模式[譯]

閉包漫談(從抽象代數(shù)及函數(shù)式編程角度)

(完)

版權(quán)聲明:自由轉(zhuǎn)載-非商用-非衍生-保持署名 |Creative Commons BY-NC-ND 3.0

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末被碗,一起剝皮案震驚了整個(gè)濱河市某宪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌锐朴,老刑警劉巖兴喂,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異焚志,居然都是意外死亡衣迷,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)酱酬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)壶谒,“玉大人,你說(shuō)我怎么就攤上這事膳沽『共耍” “怎么了让禀?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)陨界。 經(jīng)常有香客問(wèn)我巡揍,道長(zhǎng),這世上最難降的妖魔是什么普碎? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任吼肥,我火速辦了婚禮,結(jié)果婚禮上麻车,老公的妹妹穿的比我還像新娘缀皱。我一直安慰自己,他們只是感情好动猬,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布啤斗。 她就那樣靜靜地躺著,像睡著了一般赁咙。 火紅的嫁衣襯著肌膚如雪钮莲。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天彼水,我揣著相機(jī)與錄音崔拥,去河邊找鬼。 笑死凤覆,一個(gè)胖子當(dāng)著我的面吹牛链瓦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播盯桦,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼慈俯,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了拥峦?” 一聲冷哼從身側(cè)響起贴膘,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎略号,沒(méi)想到半個(gè)月后刑峡,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡玄柠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年氛琢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片随闪。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡阳似,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出铐伴,到底是詐尸還是另有隱情撮奏,我是刑警寧澤俏讹,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站畜吊,受9級(jí)特大地震影響泽疆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜玲献,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一殉疼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧捌年,春花似錦瓢娜、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至托酸,卻和暖如春褒颈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背励堡。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工谷丸, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人应结。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓刨疼,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親摊趾。 傳聞我的和親對(duì)象是個(gè)殘疾皇子币狠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容