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ě)了一些觀點(diǎn)來(lái)解釋為何不用 AMD,然而,那些解釋和觀點(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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容