簡述JavaScript模塊化編程(一)

在早期編寫JavaScript時,咱們只需在<script>標籤內寫入JavaScript的代碼就能夠知足咱們對頁面交互的須要了。但隨着時間的推移,時代的發展,本來的那種簡單粗暴的編寫方式所帶來的諸如邏輯混亂,頁面複雜,可維護性差,全局變量暴露等問題接踵而至,前輩們爲了解決這些問題提出了很種的解決方案,其中之一就是JavaScript模塊化編程。總的來講,它有如下四種優勢:css

  1. 解決項目中的全局變量污染的問題。
  2. 開發效率高,有利於多人協同開發。
  3. 職責單一,方便代碼複用和維護 。
  4. 解決文件依賴問題,無需關注引用文件的順序。

先行者CommonJs

2009年Node.js橫空出世,將JavaScript帶到了服務器端領域。而對於服務器端來講,沒有模塊化那但是不行的。所以CommonJs社區的大牛們開始發力了,制定了一個與社區同名的關於模塊化的規範——CommonJs。它的規範主要以下:前端

  1. 模塊的標識應遵循的規則(書寫規範)。
  2. 定義全局函數require,經過傳入模塊標識來引入其餘模塊,執行的結果即爲別的模塊暴露出來的API。
  3. 若是被require函數引入的模塊中也包含依賴,那麼依次加載這些依賴。
  4. 若是引入模塊失敗,那麼require函數應該報一個異常。
  5. 模塊經過變量exports來向外暴露API,exports只能是一個對象,暴露的API須做爲此對象的屬性。

根據CommonJS規範的規定,每一個文件就是一個模塊,有本身的做用域,也就是在一個文件裏面定義的變量、函數、類,都是私有的,對其餘文件是不可見的。通俗來說,就是說在模塊內定義的變量和函數是沒法被其餘的模塊所讀取的,除非定義爲全局對象的屬性。jquery

1 // addA.js
2 const a = 1; 3 const addA = function(value) { 4   return value + a; 5 }

上面代碼中,變量a和函數addA,是當前文件addA.js私有的,其餘文件不可見。若是想在多個文件中分享變量a,必須定義爲global對象的屬性:ajax

1 global.a = 1;

這樣咱們就能在其餘的文件中訪問變量a了,但這種寫法不可取,輸出模塊對象最好的方式是module.exports:編程

1 // addA.js
2 var a = 1; 3 var addA = function(value) { 4   return value + x; 5 } 6 module.exports.addA = addA;

上面代碼經過module.exports對象輸出了一個函數,該函數就是模塊外部與內部通訊的橋樑。加載模塊須要使用require方法,該方法讀取一個文件並執行,最後返回文件內部的module.exports對象。數組

1 var example = require('./addA.js'); 2 console.log(example.addA(1));  //2

CommonJs看起來是一個很不錯的選擇,擁有模塊化所須要的嚴格的入口和出口,看起來一切都很美好,但它的一個特性卻決定了它只能在服務器端大規模使用,而在瀏覽器端發揮不了太大的做用,那就是同步!這在服務器端不是什麼問題,但放在瀏覽器端就出現問題了,由於文件都放在服務器上,若是網速不夠快的話,前面的文件若是沒有加載完成,瀏覽器就會失去響應!所以爲了在瀏覽器上也實現模塊化得來個異步的模塊化才行!根據這個需求,咱們的下一位主角——AMD就產生了!瀏覽器

AMD 異步模塊定義

AMD的全名叫作:Asynchronous Module Definition即異步模塊定義。它採用了異步的方式來加載模塊,而後在回調函數中執行主邏輯,所以模塊的加載不影響它後面的模塊的運行。它的規範以下:服務器

1 define(id?, dependencies?, factory);
  1. 用全局函數define來定義模塊;
  2. id爲模塊標識,聽從CommonJS Module Identifiers規範
  3. dependencies爲依賴的模塊數組,在factory中需傳入形參與之一一對應
  4. 若是dependencies的值中有"require"、"exports"或"module",則與commonjs中的實現保持一致
  5. 若是dependencies省略不寫,則默認爲["require", "exports", "module"],factory中也會默認傳入require,exports,module
  6. 若是factory爲函數,模塊對外暴漏API的方法有三種:return任意類型的數據、exports.xxx=xxx、module.exports=xxx
  7. 若是factory爲對象,則該對象即爲模塊的返回值

具體分析AMD咱們經過require.js來進行。require.js是一個很是小巧的JavaScript模塊載入框架,是AMD規範最好的實現者之一,require.js的出現主要是來解決兩個問題:框架

  1. 實現JavaScript文件的異步加載,避免網頁失去響應。
  2. 管理模塊的依賴性,管理模塊的相互獨立性,也就是咱們常說的低耦合,這有利於代碼的編寫與維護。

使用require.js咱們首先要加載它,爲了不瀏覽器未響應,咱們在後面能夠加上async,告訴瀏覽器這個文件須要異步加載(IE不支持該屬性,因此須要把defer也加上):異步

1 <script src="js/require.js" defer async="true" ></script>

定義模塊時,在require.js中咱們可使用define,但define對於須要定義的模塊是不是獨立的模塊的寫法是不一樣;所謂的獨立模塊就是指不依賴於其餘模塊的模塊,而非獨立模塊就是指不依賴於其餘模塊的模塊。

define在定義獨立模塊時有兩種寫法,一種是直接定義對象;另外一種是定義一個函數,在函數內的返回值就是輸出的模塊了:

 1 define({  2     method1: function() {},  3     method2: function() {},  4 });  5 //等價於
 6 define(function () {  7     return {  8         method1: function() {},  9         method2: function() {}, 10  } 11 });

若是define定義非獨立模塊,那麼它的語法就規定必定是這樣的:

 1 define(['module1', 'module2'], function(m1, m2) {  2 
 3     return {  4         method: function() {  5  m1.methodA();  6  m2.methodB();  7  }  8  }  9 
10 });

define在這個時候接受兩個參數,第一個參數是module是一個數組,它的成員是咱們當前定義的模塊所依賴的模塊,只有順利加載了這些模塊,咱們新定義的模塊才能成功運行。第二個參數是一個函數,當前面數組內的成員所有加載完以後它才運行,它的參數m與前面的module是一一對應的。這個函數必須返回一個對象,以供其餘模塊調用,須要注意的是,回調函數必須返回一個對象,這個對象就是你定義的模塊。

在加載模塊方面,AMD和CommonJs都是使用require。require.js也一樣如此,它要求兩個參數:module,callback:

require([module], callback);

第一個參數[module],是一個數組,裏面的成員就是須要加載的模塊;第二個參數callback,則是加載成功以後的回調函數。
require方法自己也是一個對象,它帶有一個config方法,用來配置require.js運行參數。config方法接受一個對象做爲參數。

 1 //別名配置
 2 requirejs.config({  3  paths: {  4         jquery: [   //若是第一個路徑不能完成加載,就調到第二個路徑繼續進行加載
 5             '//cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js',  6             'lib/jquery'   //本地文件中不須要寫.js
 7  ]  8  }  9 }); 10 
11 //引入模塊,用變量$表示jquery模塊
12 requirejs(['jquery'], function ($) { 13     $('body').css('background-color','black'); 14 });

雖然require.js實現了異步的模塊化,但它仍然有一些不足的地方,在使用require.js的時候,咱們必需要提早加載全部的依賴,而後纔可使用,而不是須要使用時再加載,使得初次加載其餘模塊的速度較慢,提升了開發成本。

CMD 通用模塊定義

CMD的全稱是Common Module Definition,即通用模塊定義。它是由螞蟻金服的前端大佬——玉伯提出來的,實現的JavaScript庫爲sea.js。它和AMD的require.js很像,但加載方式不一樣,它是按需就近加載的,而不是在模塊的開始所有加載完成。它有如下兩大核心特色:

  1. 簡單友好的模塊定義規範:Sea.js 遵循 CMD 規範,能夠像 Node.js 通常書寫模塊代碼。
  2. 天然直觀的代碼組織方式:依賴的自動加載、配置的簡潔清晰,可讓咱們更多地享受編碼的樂趣。

在CMD規範中,一個文件就是一個模塊,代碼書寫的格式是這樣的:

define(factory);

當factory爲函數時,表示模塊的構造方法,執行該方法,能夠獲得該模塊對外提供的factory接口,factory 方法在執行時,默認會傳入三個參數:require、exports 和 module:

 1 // 全部模塊都經過 define 來定義
 2 define(function(require, exports, module) {  3 
 4   // 經過 require 引入依賴
 5   var $ = require('jquery');  6   var Spinning = require('./spinning');  7 
 8   // 經過 exports 對外提供接口
 9   exports.doSomething = ... 10 
11   // 或者經過 module.exports 提供整個接口
12   module.exports = ... 13 
14 });

它與AMD的具體區別其實咱們也能夠經過代碼來表現出來,AMD須要在模塊開始前就將依賴的模塊加載出來,即依賴前置;而CMD則對模塊按需加載,即依賴就近,只有在須要依賴該模塊的時候再require就好了:

 1 // AMD規範
 2 define(['./a', './b'], function(a, b) {  // 依賴必須一開始就寫好 
 3  a.doSomething()  4    // 此處略去 100 行 
 5  b.doSomething()  6  ...  7 });  8 // CMD規範
 9 define(function(require, exports, module) { 10    var a = require('./a') 11  a.doSomething() 12    // 此處略去 100 行 
13    var b = require('./b') 14    // 依賴能夠就近書寫 
15  b.doSomething() 16    // ... 
17 });

須要注意的是Sea.js的執行模塊順序也是嚴格按照模塊在代碼中出現(require)的順序。

從運行速度的角度來說,AMD雖然在第一次使用時較慢,但在後面再訪問時速度會很快;而CMD第一次加載會相對快點,但後面的加載都是從新加載新的模塊,因此速度會慢點。總的來講, require.js的作法是並行加載全部依賴的模塊, 等完成解析後, 再開始執行其餘代碼, 所以執行結果只會"停頓"1次, 而Sea.js在完成整個過程時則是每次須要相應模塊都須要進行加載,這期間會停頓是屢次的,所以require.js從總體而言相對會比Sea.js要快一些。

相關文章
相關標籤/搜索