淺談模塊化的JavaScript


  模塊化JavaScript之風早已席卷而來,CommonJSAMDNodeJSRequireJSSeaJScurljs 等模塊化的JavaScript概念及庫撲面而來,不得不承認,對於前端JavaScript代碼的組織編寫是一次偉大的變革。本文主要參考snandy的有關modular js系列文章,對SeaJS和RequireJS做一個系統的深入分析及對比。

  一、我們為什么要用模塊化的JavaScript

  相信大家也都經歷了“過程式的JavaScript”、“面向對象的JavaScript”,再到現在的“面向模塊的JavaScript”。這個“面向模塊的JavaScript”到底能走多久,我們拭目以待。
  但是我們需要知道,我們為什么要擁抱模塊化的JavaScript,它到底有什么奇妙之處,讀一下這一篇文章吧——前端模塊化開發的價值,OK,明白了,它解決了兩大問題——
  其一、惱人的命名沖突:
    這個確實夠煩人,為了解決之,我們動用閉包作用域、我們動用命名空間,再或者...
  其二、煩瑣的文件依賴:
    這個問題更不必說,我們做前端支持的,常常要替后端同學們解決這類問題,我們自己也常常忽略這類問題。平常的開發中,我們要對我們的JS文件的加載順序要小心加小心。一個字——確實煩。
  好,現在,我們有SeaJS了,這兩大問題變得很輕松就能搞定了,開心哪。
  針對第一個問題:通過 exports 暴露接口。這意味着不需要命名空間了,更不需要全局變量。這是一種徹底的命名沖突解決方案。
  針對第二個問題:通過 require 引入依賴。這可以讓依賴內置,開發者只需關心當前模塊的依賴,其他事情 SeaJS 都會自動處理好。對模塊開發者來說,這是一種很好的關注度分離,能讓程序員更多地享受編碼的樂趣。

  二、 瀏覽器中的模塊規范AMD是怎么誕生的?

  SeaJS我們領教過了,但是對於它的模塊化寫法,到底是咋回事兒?我們先看看module寫法的演變。直接去看JavaScript中模塊“寫法”,使用統一的風格來寫模塊,非nodeJS打頭陣不可,以下是node中是寫模塊的一個示例。

  1> math.js

exports.add = function() { var sum = 0, i = 0, args = arguments, l = args.length; while (i < l) { sum += args[i++]; } return sum; };

  2> increment.js

var add = require('math').add; exports.increment = function(val) { return add(val, 1); };

  3> main.js,該文件為入口文件

var inc = require('increment').increment; var a = 1; inc(a); // 2

  從以上代碼示例可以看到——

1> node要求一個js文件對應一個模塊。可以把該文件中的代碼想象成是包在一個匿名函數中,所有代碼都在匿名函數中,其它模塊不可訪問除exports外的私有變量

2> 使用exports導出API

3> 使用require加載其它模塊

  CommonJS module基本要求如下——

1> 標示符require,為一個函數,它僅有一個參數為字符串,該字符串須遵守Module Identifiers的6點規定

2> require方法返回指定的模塊API

3> 如果存在依賴的其它模塊,那么依次加載

4> require不能返回,則拋異常

5> 僅能使用標示符exports導出API 

  打頭陣的NodeJS真是享譽甚高,前端的我們是不是也可以使用同樣的模塊寫法寫模塊呢?當然nodeJS是最好的效仿對象。因為前后端有一個統一的方式寫JS模塊豈不樂哉!可是,事與願違,讀這個文章吧——Node.js模塊風格在瀏覽器中的嘗試,深入理解之,便能明白,為什么NodeJS模塊寫法為什么不適合瀏覽器端。為了解決之,便出現了Modules/Wrappings ,顧名思義包裹的模塊。該規范約定如下——

 1> 定義模塊用module變量,它有一個方法declare

2> declare接受一個函數類型的參數,如稱為factory

3> factory有三個參數分別為require、exports、module

4> factory使用返回值和exports導出API

5> factory如果是對象類型,則將該對象作為模塊輸出 

  Modules/Wrappings的出現使得瀏覽器中實現它變得可能,包裹的函數作為回調。即使用script tag作為模塊加載器,script完全下載后去回調,回調中進行模塊定義。
  接着,也就產生了AMD(全稱為異步模塊定義)規范了(讀AMD:瀏覽器中的模塊規范)。從名稱上看便知它是適合script tag的。也可以說AMD是專門為瀏覽器中JavaScript環境設計的規范。它吸取了CommonJS的一些優點,但又不照搬它的格式。開始AMD作為CommonJS的transport format 存在,因無法與CommonJS開發者達成一致而獨立出來。
  AMD開創性的提出了自己的模塊風格。但后來又做了妥協,兼容了 CommonJS Modules/Wrappings 。

define(function(require, exports, module) { var base = require('base'); exports.show = function() { // todo with module base
 } });

  不考慮多了一層函數外,格式和Node.js是一樣的。使用require獲取依賴模塊,使用exports導出API。
  除了define外,AMD還保留一個關鍵字require。require 作為規范保留的全局標識符,可以實現為 module loader。
  目前,實現AMD的庫有RequireJS 、curl 、Dojo 、bdLoadJSLocalnet 、Nodules 等。

  三、SeaJS與RequireJS的區別

  AMD設計出一個簡潔的寫模塊API:define(id?, dependencies?, factory);
  我們再看一下SeaJS的模塊寫法——
  SeaJS默認使用全局的define函數寫模塊(可把define當成語法關鍵字),define定義了三個形參id, deps, factory。
  define(id?, deps?, factory);

  搞不懂SeaJS和 RequireJS define的區別。它們都有個全局的define,形參都是三個,且對應的形參名也一樣,會誤認為SeaJS也是AMD的實現。事實上SeaJS和RequireJS的define前兩個參數的確一樣。id都為字符串,都遵循 Module Identifiers。deps都是指依賴模塊,類型都為數組。區別僅在於第三個參數factory,雖然類型也都是函數,但factory的參數意義卻不同。
RequireJS中factory的參數有兩種情況。

  a、和deps(數組)元素一一對應。即deps有幾個,factory的實參就有幾個。(官方推薦的模塊定義方法)

define(['a', 'b'], function(a, b){ // todo
});

  b、固定為require,exports, module(modules/wrappings格式)。

define(function(require, exports, module){ // todo
});

  這種方式是RequireJS后期向 Modules/Wrappings 的妥協,即兼容了它。而SeaJS的define僅支持RequireJS的第二種寫法:Modules/Wrappings。

  注意:SeaJS遵循的是 Modules/Wrappings 和 Modules/1.1.1。這兩個規范中都沒有提到define關鍵字,Modules/Wrapping中要求定義模塊使用module.declare而非define。而恰恰只有AMD規范中有define的定義。即雖然SeaJS不是AMD的實現,但它卻采用了讓人極容易誤解的標識符define。

  對於SeaJS和RequireJS的其他差異,可以讀SeaJS與RequireJS的異同

  兩者相同之處——

 RequireJS 和 SeaJS 都是模塊加載器,倡導模塊化開發理念,核心價值是讓 JavaScript 的模塊化開發變得簡單自然。

  兩者不同之處——

1> 定位有差異。RequireJS 想成為瀏覽器端的模塊加載器,同時也想成為 Rhino / Node 等環境的模塊加載器。SeaJS 則專注於 Web 瀏覽器端,同時通過 Node 擴展的方式可以很方便跑在 Node 環境中。
2> 遵循的規范不同。RequireJS 遵循 AMD(異步模塊定義)規范,SeaJS 遵循 CMD (通用模塊定義)規范。規范的不同,導致了兩者 API 不同。SeaJS 更貼近 CommonJS Modules/1.1 和 Node Modules 規范。
3> 推廣理念有差異。RequireJS 在嘗試讓第三方類庫修改自身來支持 RequireJS,目前只有少數社區采納。SeaJS 不強推,采用自主封裝的方式來“海納百川”,目前已有較成熟的封裝策略。
4> 對開發調試的支持有差異。SeaJS 非常關注代碼的開發調試,有 nocache、debug 等用於調試的插件。RequireJS 無這方面的明顯支持。
5> 插件機制不同。RequireJS 采取的是在源碼中預留接口的形式,插件類型比較單一。SeaJS 采取的是通用事件機制,插件類型更豐富。 

  四、AMD和CMD的區別

  接着引深來看,大家都知道SeaJS遵循CMD(通用模塊定義),RequireJS遵循AMD,那么CMD和AMD到底有什么區別?
  上面描述過AMD模塊定義規范,接着我們看看CMD模塊定義規范,可大致了解一下CMD到底是怎樣的規范。對於兩者的差異,讀讀玉泊對它的回答吧——AMD和CMD區別有哪些? 

AMD 是 RequireJS 在推廣過程中對模塊定義的規范化產出。
CMD 是 SeaJS 在推廣過程中對模塊定義的規范化產出。
類似的還有 CommonJS Modules/2.0 規范,是 BravoJS 在推廣過程中對模塊定義的規范化產出。
還有不少⋯⋯

這些規范的目的都是為了 JavaScript 的模塊化開發,特別是在瀏覽器端的。
目前這些規范的實現都能達成瀏覽器端模塊化開發的目的。
區別:
  1. 對於依賴的模塊,AMD 是提前執行,CMD 是延遲執行。不過 RequireJS 從 2.0 開始,也改成可以延遲執行(根據寫法不同,處理方式不同)。CMD 推崇 as lazy as possible.
  2. CMD 推崇依賴就近,AMD 推崇依賴前置。看代碼:
// CMD
define(function(require, exports, module) {
  var a = require('./a')
  a.doSomething()
  // 此處略去 100 行
  var b = require('./b') // 依賴可以就近書寫
  b.doSomething()
  // ... 
})

// AMD 默認推薦的是
define(['./a', './b'], function(a, b) { // 依賴必須一開始就寫好
  a.doSomething()
  // 此處略去 100 行
  b.doSomething()
  ...
}) 

雖然 AMD 也支持 CMD 的寫法,同時還支持將 require 作為依賴項傳遞,但 RequireJS 的作者默認是最喜歡上面的寫法,也是官方文檔里默認的模塊定義寫法。
3. AMD 的 API 默認是一個當多個用,CMD 的 API 嚴格區分,推崇職責單一。比如 AMD 里,require 分全局 require 和局部 require,都叫 require。CMD 里,沒有全局 require,而是根據模塊系統的完備性,提供 seajs.use 來實現模塊系統的加載啟動。CMD 里,每個 API 都簡單純粹。
4. 還有一些細節差異,具體看這個規范的定義就好,就不多說了。

  五、總結
  寫了挺多,當然很多都是摘錄的,不過把模塊化來源的思路搞定,收獲也就有了。我再理順一下——

  1> 我們為什么要用模塊化的JavaScript
    最主要的是解決命名沖突、解決文件依賴這兩大問題。
  2> 瀏覽器中的模塊規范AMD是怎么誕生的?
    使用script tag作為模塊加載器,script完全下載后去回調,回調中進行模塊定義。
  3> SeaJS與RequireJS的區別
    RequireJS 遵循的是 AMD(異步模塊定義)規范,SeaJS 遵循的是 CMD (通用模塊定義)規范。

  4> AMD和CMD的區別
     (1)對於依賴的模塊,AMD 是提前執行,CMD 是延遲執行。不過 RequireJS 從 2.0 開始,也改成可以延遲執行(根據寫法不同,處理方式不同)。CMD 推崇 as lazy as possible.
     (2)CMD 推崇依賴就近,AMD 推崇依賴前置。(這一點是非常重要的區別)
     (3)AMD 的 API 默認是一個當多個用,CMD 的 API 嚴格區分,推崇職責單一
    需要記住——AMD 是 RequireJS 在推廣過程中對模塊定義的規范化產出。CMD 是 SeaJS 在推廣過程中對模塊定義的規范化產出。

  六、引深
     本文僅僅講述了前端Javascript的模塊化開發思想,但是對於其他的HTML和CSS,我們是否也可以采用模塊化話的開發思想來引導呢?答案是肯定的。尤其在前端多人合作開發的情況下,這種模塊化的開發模式會讓我們愛不釋手。這里我只細讀過這樣一篇文章,前端開發:模塊化 — 高效重構。該論題還在研究中,敬請期待...

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM