OMD: javascript模塊化開發兼容CommonJS, AMD, CMD 以及 原生 JS

有更新,請到github上看源碼javascript

什麼是OMD

在node.js流行起來以前,javascript的開發方式都是函數式的順序依賴關係,直到node火起來。CommonJS其實首先提供了一個豐富的可擴展的庫,在此基礎上提供可應用的API,在這些API的基礎上,逐漸造成一套規範,使得javascript的開發在這些規範的基礎上進行。它的重要特徵就是運行開發者模塊化的開發javascript,提供一個模塊,這個模塊對外提供接口,有一個入口去調動全部的模塊協同完成不一樣的功能。node就是在此規範上創建起來的服務端框架。html

node能夠被認爲是應用程序開發的一個案例,它是運行在服務器上的javascript。而是否能夠在瀏覽器上面也遵循CommonJS規範呢?爲了達到這個目的,requirejs火起來,與它對應的是AMD規範。實際上,AMD規範是CommonJS的一個兼容方案,requirejs但願創建一個既知足應用程序,又知足瀏覽器運行的規範。AMD也逐漸被開發者接受,國外不少javascript發佈產品,基本上都遵循或支持amd規範。前端

seajs是專一於瀏覽器的模塊化javascript開發框架,和前二者不一樣的是,它只專一於瀏覽器端,並且更優雅靈活,它所遵循的是CMD規範,其創始人爲中國的前端大牛玉伯。自此,三家分立,各有裨益,javascript的模塊化開發規範已經被創立完畢,之後就將其逐漸完善,並實現更多的應用。java

在此基礎上,有人試圖實現兼容。也就是本身的代碼既能在node上運行,也能在符合amd規範的代碼環境中運行。因而產生了umd,其實umd並不算規範,而只能算一種兼容方案,它能夠兼容CommonJS和AMD。但國內更多的實際上是使用seajs,包括淘寶、騰訊的部分產品,都是使用seajs框架,而react等的跑火,有理由相信,將來的前端開發必定是模塊化趨勢的。node

就目前而言,爲了能兼容commonjs amd cmd以及原生的javascript,我結合了前人的經驗,發佈了omd,它也不是什麼規範,只是一套兼容方案罷了。react

omd的目的,就是但願開發javascript按照這個規則來完成代碼,就能使它在模塊化開發環境下能跑,在普通的javascript或jquery代碼中也能跑。它就是一套兼容方案,目前兼容的有module.exports、amd、cmd以及原生支持。jquery

OMD的源碼

首先,我須要將源碼放出來,全部的讀者能夠先簡單的看下它的內容,大體瞭解它的運行規則。在github上follow這個項目,隨時瞭解最新的一些變化。git

OMD原理詳解

在《javascript當即執行某個函數:插件中function(){}()再思考》一文中,我詳細闡述了!function(fun){}(function($){});,所以這裏就不詳細闡述了。github

兼容amd和cmd瀏覽器

if(typeof define == 'function' && (define.amd != undefined || define.cmd != undefined) {
  define(function() {
    return fun();
  });
}

上面這段代碼,僅在amd或cmd規範下使用時才執行define。咱們這裏把範圍縮小到requirejs和sea.js。對於requirejs而言,define和define.amd是可見的,所以執行define(function(){});,一樣的道理,seajs也會執行。可是做爲兩種不一樣的規範,爲什麼能夠以相同的代碼返回呢?

在requirejs中,一個模塊能夠如此去定義:

define({});
define(factory);
define(id,dependencies,factor);
define(dependencies,factor)

在seajs中也差很少,可是也有不一樣之處:

define({});
define(factory);
define(id,dependencies,factory);
define(id,factory);

前面三種都是同樣的,可是細節上也有所不一樣,第一種以對象的方式定義並不能解決咱們插件中接口傳遞的目的,所以不考慮。剩下的就只有兩種相同的形式,可是就像前文說到的同樣,OMD是爲了實現兼容,也就是說它必須做爲框架被應用,框架的基礎代碼具備穩定性,不能讓用戶改來改去,不然也就失去了規範的意義。而在剩下的兩種中,define(id,dependencies,factory)的前兩個變量,都須要本身去定義,所以,也被排除。最終也就只有define(factory)這種形式被咱們採用。

define(function(require,exports,module){
  var a = require('a');
});

這種代碼形式在require.js和sea.js中均可以使用,但是在sea.js中,可使用exports.xxx = function(){}來提供接口,但是在require.js中,不得不採用return的形式,幸虧,在sea.js中,return也是有效的。因此,最終,咱們選擇了return的形式向外提供接口:

define(function(){
    return {
        fun1 : function(a,b) {},
        fun2 : function(c,d) {}
    }; // return的結果爲一個對象
});

在sea.js或require.js中:

define(function(){
    var $omd = require('omd');
    var fun1 = $omd.fun1(12,34);
});

這樣,在omd.js中寫的插件所提供的模塊接口,就能夠被使用了。

兼容comomjs

當define沒有被定義的時候,說明跟sea.js和require.js沒有任何關係了,這個時候,咱們要檢查是否支持node.js。

若是是在node.js環境下運行,那麼module和exports是一個由核心庫提供的全局變量。所以,只須要將插件提供的接口賦值給module.exports,就完成了當前文件(模塊)所提供的接口了。

兼容原生的javascript

當以上狀況都不知足的狀況下,實際上,你所提供的接口,就是一個函數。你提供了一個fun1的接口,你就能夠在其餘javascript代碼中執行fun1()函數。

全局變量$和局部變量$

把$做爲變量名,最大的好處是兼容jquery和zepto。在第一個function(factory){}中,我兩次使用到了factory($),在這裏,$沒有被事先聲明過,所以用了一個if(typeof $ === 'undefined')作判斷。可是若是當前環境下已經加載了jquery或zepto,$就是一個全局變量,實際上它是window.$。所以,在執行factory($)時,其實是把$做爲參數傳遞給了factory()。

而在前面那篇關於當即執行函數的文章中我已經講過了,在這裏的factory()實際上就是第二個咱們要真正用來寫插件代碼的function($){},要理解這一點,你必須讀懂上面那篇文章。

在factory($)中,$若是表明jquery或zepto,那麼它其實是一個全局變量。而到了function($),$實際上成爲函數的參數,成爲一個局部變量。在function($){}中,雖然你可使用$('#div1')進行選擇,但你要知道,這裏的$並不是全局變量window.$,而是傳遞而來的,是它的引用。下文還會提到,要使用 var jQuery = $; 這樣詭異的代碼來處理某些狀況。

factory($)返回值問題

在第一次使用factory($)時,return factory($)。第二次時,ex = factory($)。從值的返回角度講,factory($)必然存在一個return。具體是什麼意義呢?

factory($)的返回值,其實就是插件提供的對外接口,而實際上,就是一個對象Object。咱們能夠在第一次return factory($);的時候,也先執行一次賦值操做:

define(function(){
    var ex = factory($);
    return ex;
});

前面已經講過了,amd和cmd在define的function中return,其實是模塊對外提供接口的一種方式,而這種方式,必須保證以對象的形式返回。所以,在插件代碼中,你能夠看到,我首先定義的是一個ex = {},而後執行return ex;從而對外提供了接口。

而在node.js環境下,只須要將這個ex返回值賦予exports便可完成該模塊的接口。在原生的JavaScript環境下,沒有接口這種概念,對外提供的,則是函數或對象屬性,將它賦予window對象,就至關於提供了一個全局函數或全局變量。

OMD開發規則

利用omd開發兼容各個規範的插件(模塊)時,只須要在// 真正的插件代碼都在這裏這句註釋後面撰寫插件代碼便可,無需像其餘教程所示同樣,寫一個(function($){}(jQuery)),直接寫插件內容便可。若是無需對外提供接口,則寫完插件代碼就能夠完成開發。

關於var jQuery = $

在插件代碼中,一些插件並無使用$,而是使用jQuery。可是前文已經提到了,在這個函數內部,jQuery是未定義的,$是傳遞過來的變量,所以,將$賦值給jQuery,則讓jQuery從新有效。

若是須要對外提供接口,則在// 接口開始後面使用對象屬性的方式,將接口賦值給ex。注意一點,ex返回後,你不能夠經過接口改變插件內部的變量,這個接口是對外接口。

利用OMD開發的步驟

1.在開始開發插件以前,將omd.js的代碼框架拷貝到你的項目(插件)文件中;

2.在註釋中對應的位置開始撰寫你的腳步代碼

3.在註釋中指出接口輸出的位置處,經過ex.fun = function(){}的方式返回接口

4.完成代碼寫做以後,利用JavaScript壓縮工具,淨化和壓縮你的Javascript代碼

OMD使用要點

和sea.js不一樣,require.js的全部模塊都是異步加載,這意味着你不能按照以往的方式,經過先加載哪一個模塊,而後加載另一個模塊來肯定它們的依賴關係。不幸的是,全部的jquery插件都要依賴jquery庫。所以,在require中須要解決好這個依賴問題。依賴的用法只有在define的參數中,前面已經提到了define的幾種用法,咱們來看下具體的實現方法:

define(['jquery'],function($){
    require(['plugin.omd.js'],function($plugin){
        $('#test').plugin();
        $plugin.p('#test');
    });
});

上面的代碼中,define(['jquery'])首先肯定了這個模塊依賴於jquery,並將其返回接口以$做爲變量。在這個基礎上,再去require(omd.js),則可讓omd.js中的對jQuery的依賴能夠實現,omd.js中才能夠正常使用jquery。

不過,從require.js的設計上,模塊之間都是異步加載的,若是按照上述方法解決依賴問題,性能上比sea.js要差不少。

我已經將這個代碼放到github上,裏面有demo,你能夠看下plugin.js和main.js,來看看具體的用法。

相關文章
相關標籤/搜索