1. 程式人生 > >淺談JavaScript模組化

淺談JavaScript模組化

2006年,ajax的概念被提出,前端擁有了主動向服務端傳送請求並操作返回資料的能力,隨著Google將此概念的發揚光大,傳統的網頁慢慢的向“富客戶端”發展。前端的業務邏輯越來越多,程式碼也越來越多,於是一些問題就暴漏了出來:

1. 全域性變數的災難

小明定義了 i=1

小剛在後續的程式碼裡:i=0

小明在接下來的程式碼裡:if(i==1){…} //悲劇

 2. 函式命名衝突

專案中通常會把一些通用的函式封裝成一個檔案,常見的名字有utils.js、common.js…

小明定義了一個函式:function formatData(){   }

小剛想實現類似功能,於是這麼寫:function formatData2(){   }

小光又有一個類似功能,於是:function formatData3(){   }

……

避免命名衝突就只能這樣靠醜陋的方式人肉進行。

 3. 依賴關係不好管理

b.js依賴a.js,標籤的書寫順序必須是

<script type="text/javascript" src="a.js"></script>
<script type="text/javascript" src="b.js"></script>

順序不能錯,也不能漏寫某個。在多人開發的時候很難協調。

然而,JavaScript卻沒有為組織程式碼提供任何明顯幫助,甚至沒有類的概念,更不用說模組(module)了,那麼什麼是模組呢?

一個模組就是實現特定功能的檔案,有了模組,我們就可以更方便地使用別人的程式碼,想要什麼功能,就載入什麼模組。模組開發需要遵循一定的規範,否則就都亂套了。

模組化面臨什麼問題

從不停的嘗試中,可以歸納出js模組化需要解決哪些問題:

1. 如何安全的包裝一個模組的程式碼?(不汙染模組外的任何程式碼)

2. 如何唯一標識一個模組?

3. 如何優雅的把模組的API暴漏出去?(不能增加全域性變數)

4. 如何方便的使用所依賴的模組?

圍繞著這些問題,js模組化開始了一段艱苦而曲折的征途。

模組化開發規範: AMD CMD commonjs

源自nodejs的規範CommonJs

2009年,nodejs橫空出世,開創了一個新紀元,人們可以用js來編寫服務端的程式碼了。如果說瀏覽器端的js即便沒有模組化也可以忍的話,那服務端是萬萬不能的。

CommonJS是伺服器端模組的規範,根據CommonJS規範,一個單獨的檔案就是一個模組。每一個模組都是一個單獨的作用域,也就是說,在該模組內部定義的變數,無法被其他模組讀取,除非定義為global物件的屬性。

輸出模組變數的最好方法是使用module.exports物件。

var i = 1;
var max = 30;

module.exports = function () {
  for (i -= 1; i++ < max; ) {
    console.log(i);
  }
  max *= 1.1;
};

上面程式碼通過module.exports物件,定義了一個函式,該函式就是模組外部與內部通訊的橋樑。

載入模組使用require方法,該方法讀取一個檔案並執行,最後返回檔案內部的module.exports物件。

CommonJs的問題在於,他的載入是同步的,這在服務端很正常,但是在充滿了非同步的瀏覽器裡,就不適用了。為了適應瀏覽器,社群內部發生了分歧。

 

AMD/RequireJs的崛起與妥協

AMD 即Asynchronous Module Definition,中文名是非同步模組定義的意思。AMD的思想正如其名,非同步載入所需的模組,然後在回撥函式中執行主邏輯。這正是我們在瀏覽器端開發所習慣了的方式,其作者親自實現了符合AMD規範的requirejs,AMD/RequireJs迅速被廣大開發者所接受。

AMD規範規定用全域性函式define來定義模組,用法為define(id, dependencies, factory);其中,id為模組標識,dependencies是一個數組,數組裡邊是該模組依賴的其他模組,factory則是一個匿名函式,裡邊是該模組的邏輯。

//main.js
require(['a', 'b'], function(a, b){
     console.log('main.js執行');
     a.hello();
     $('#b').click(function(){
          b.hello();
     });
})

requireJs的問題在於,載入一個模組時,會預先載入該模組的所有依賴模組,但是這些依賴很可能一開始並不用到。同時依賴寫起來一長串,也很麻煩。比較好的是AMD保留了commonJs中的require、exprots、module3個功能,可以不把依賴都寫在dependencies中,而是在需要時使用require引入。

相容幷包的CMD/seajs

既然requirejs有上述種種不甚優雅的地方,所以必然會有新東西來完善它,這就是後起之秀seajs,seajs的作者是國內大牛淘寶前端步道者玉伯。seajs全面擁抱Modules/Wrappings規範,不用requirejs那樣回撥的方式來編寫模組。而它也不是完全按照Modules/Wrappings規範,seajs並沒有使用declare來定義模組,而是使用和requirejs一樣的define,或許作者本人更喜歡這個名字吧。(然而這或多或少又會給人們造成理解上的混淆),用seajs定義模組的寫法如下:

//main.js
define(function(require, exports, module){
     console.log('main.js執行');
     var a = require('a');
     a.hello();    
     $('#b').click(function(){
          var b = require('b');
          b.hello();
     });

});

定義模組時無需羅列依賴陣列,在factory函式中需傳入形參require,exports,module,然後它會呼叫factory函式的toString方法,對函式的內容進行正則匹配,通過匹配到的require語句來分析依賴,這樣就真正實現了commonjs風格的程式碼。

AMD和CMD 的區別

AMD和CMD最明顯的區別就是在模組定義時對依賴的處理不同 
AMD推崇依賴前置,在定義模組的時候就要宣告其依賴的模組 
CMD推崇就近依賴,只有在用到某個模組的時候再去require 

參考資料:JavaScript模組化歷程