詳解JavaScript模塊化開發

什么是模塊化開發?

前端開發中,起初只要在script標簽中嵌入幾十上百行代碼就能實現一些基本的交互效果,后來js得到重視,應用也廣泛起來了,jQuery,Ajax,Node.Js,MVC,MVVM等的助力也使得前端開發得到重視,也使得前端項目越來越復雜,然而,JavaScript卻沒有為組織代碼提供任何明顯幫助,甚至沒有類的概念,更不用說模塊(module)了,那么什么是模塊呢?

一個模塊就是實現特定功能的文件,有了模塊,我們就可以更方便地使用別人的代碼,想要什么功能,就加載什么模塊。模塊開發需要遵循一定的規范,否則就都亂套了。

根據AMD規范,我們可以使用define定義模塊,使用require調用模塊。

目前,通行的js模塊規范主要有兩種:CommonJS和AMD。

AMD規范

AMD 即Asynchronous Module Definition,中文名是“異步模塊定義”的意思。它是一個在瀏覽器端模塊化開發的規范,服務器端的規范是CommonJS

模塊將被異步加載,模塊加載不影響后面語句的運行。所有依賴某些模塊的語句均放置在回調函數中。

AMD是RequireJS在推廣過程中對模塊定義的規范化的產出。

define() 函數

AMD規范只定義了一個函數define,它是全局變量。函數的描述為:

define(id?, dependencies?, factory);

參數說明:

id:指定義中模塊的名字,可選;如果沒有提供該參數,模塊的名字應該默認為模塊加載器請求的指定腳本的名字。如果提供了該參數,模塊名必須是“頂級”的和絕對的(不允許相對名字)。

依賴dependencies:是一個當前模塊依賴的,已被模塊定義的模塊標識的數組字面量。

依賴參數是可選的,如果忽略此參數,它應該默認為["require","exports","module"]。然而,如果工廠方法的長度屬性小于3,加載器會選擇以函數的長度屬性指定的參數個數調用工廠方法。

工廠方法factory,模塊初始化要執行的函數或對象。如果為函數,它應該只被執行一次。如果是對象,此對象應該為模塊的輸出值。

模塊名的格式

模塊名用來唯一標識定義中模塊,它們同樣在依賴性數組中使用:

模塊名是用正斜杠分割的有意義單詞的字符串

單詞須為駝峰形式,或者".",".."模塊名不允許文件擴展名的形式,如“.js”

模塊名可以為"相對的"或"頂級的"。如果首字符為“.”或“..”則為相對的模塊名

頂級的模塊名從根命名空間的概念模塊解析

相對的模塊名從"require"書寫和調用的模塊解析

使用 require 和 exports

創建一個名為"alpha"的模塊,使用了require,exports,和名為"beta"的模塊:

define("alpha", ["require","exports","beta"],function(require, exports, beta) {

exports.verb =function() {returnbeta.verb();//Or:returnrequire("beta").verb();

}

});

require API介紹:https://github.com/amdjs/amdj...

AMD規范中文版:https://github.com/amdjs/amdj...

目前,實現AMD的庫有RequireJS 、curl 、Dojo 、Nodules等。

CommonJS規范

CommonJS是服務器端模塊的規范,Node.js采用了這個規范。Node.JS首先采用了js模塊化的概念。

根據CommonJS規范,一個單獨的文件就是一個模塊。每一個模塊都是一個單獨的作用域,也就是說,在該模塊內部定義的變量,無法被其他模塊讀取,除非定義為global對象的屬性。

輸出模塊變量的最好方法是使用module.exports對象。

var i =1;

varmax=30;module.exports=function() {for(i -=1; i++

console.log(i);

}max*=1.1;

};

上面代碼通過module.exports對象,定義了一個函數,該函數就是模塊外部與內部通信的橋梁。

加載模塊使用require方法,該方法讀取一個文件并執行,最后返回文件內部的module.exports對象。

CommonJS規范:http://javascript.ruanyifeng....

RequireJS和SeaJS

RequireJS由James Burke創建,他也是AMD規范的創始人。

define方法用于定義模塊,RequireJS要求每個模塊放在一個單獨的文件里。

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

SeaJS與RequireJS最大的區別:

SeaJS對模塊的態度是懶執行, 而RequireJS對模塊的態度是預執行

不明白?看這篇圖文并茂的文章吧:http://www.douban.com/note/28...

RequireJS API:http://www.requirejs.cn/docs/...

RequireJS的用法:http://www.ruanyifeng.com/blo...

為什么要用requireJS

試想一下,如果一個網頁有很多的js文件,那么瀏覽器在下載該頁面的時候會先加載js文件,從而停止了網頁的渲染,如果文件越多,瀏覽器可能失去響應。其次,要保證js文件的依賴性,依賴性最大的模塊(文件)要放在最后加載,當依賴關系很復雜的時候,代碼的編寫和維護都會變得困難。

RequireJS就是為了解決這兩個問題而誕生的:

(1)實現js文件的異步加載,避免網頁失去響應;

(2)管理模塊之間的依賴性,便于代碼的編寫和維護。

RequireJS文件下載:http://www.requirejs.cn/docs/...

AMD和CMD

CMD(Common Module Definition) 通用模塊定義。該規范明確了模塊的基本書寫格式和基本交互規則。該規范是在國內發展出來的。AMD是依賴關系前置,CMD是按需加載。

在 CMD 規范中,一個模塊就是一個文件。代碼的書寫格式如下:

define(factory);

factory為函數時,表示是模塊的構造方法。執行該構造方法,可以得到模塊向外提供的接口。factory 方法在執行時,默認會傳入三個參數:require、exports 和 module:

define(function(require, exports,module){

// 模塊代碼

});

require是可以把其他模塊導入進來的一個參數,而export是可以把模塊內的一些屬性和方法導出的。

CMD規范地址:https://github.com/seajs/seaj...

AMD 是 RequireJS 在推廣過程中對模塊定義的規范化產出。CMD是 SeaJS 在推廣過程中對模塊定義的規范化產出。

對于依賴的模塊,AMD是提前執行,CMD是延遲執行。

AMD:提前執行(異步加載:依賴先執行)+延遲執行CMD:延遲執行(運行到需加載,根據順序執行)

CMD 推崇依賴就近,AMD 推崇依賴前置。看如下代碼:

// CMDdefine(function(require, exports, module) {vara= require('./a')a.doSomething()// 此處略去 100 行varb= require('./b')// 依賴可以就近書寫b.doSomething()// ...})// AMD 默認推薦的是define(['./a','./b'], function(a, b) {// 依賴必須一開始就寫好a.doSomething()// 此處略去 100 行b.doSomething()

...

})

另外一個區別是:

AMD:API根據使用范圍有區別,但使用同一個api接口CMD:每個API的職責單一

AMD的優點是:異步并行加載,在AMD的規范下,同時異步加載是不會產生錯誤的。

CMD的機制則不同,這種加載方式會產生錯誤,如果能規范化模塊內容形式,也可以

jquery1.7以上版本會自動模塊化,支持AMD模式:主要是使用define函數,sea.js雖然是CommonJS規范,但卻使用了define來定義模塊

所以jQuery已經自動模塊化了

seajs.config({'base':'/','alias':{'jquery':'jquery.js'//定義jQuery文件}

});

define函數和AMD的define類似:

define(function(require, exports,module{

//先要載入jQuery的模塊var$ =require('jquery');//然后將jQuery對象傳給插件模塊require('./cookie')($);//開始使用 $.cookie方法});

sea.js如何使用?

- 引入sea.js的庫

- 如何變成模塊?

- define

-3.如何調用模塊?

-exports

-sea.js.use

-4.如何依賴模塊?

-require

define(function (require,exports,module) {//exports : 對外的接口//requires : 依賴的接口require('./test.js');//如果地址是一個模塊的話,那么require的返回值就是模塊中的exports

})

sea.js 開發實例

鼠標拖拽的模塊化開發實踐#div1{width:200px;height:200px;background:black;position:absolute;display:none;}#div2{width:30px;height:30px;background:yellow;position:absolute;bottom:0;right:0;}#div3{width:100px;height:100px;background:blue;position:absolute;right:0;top:0;}//A同事 :seajs.use('./main.js');

A同事

//A同事寫的main.js:define(function(require,exports,module) {varoInput =document.getElementById('input1');varoDiv1 =document.getElementById('div1');varoDiv2 =document.getElementById('div2');varoDiv3 =document.getElementById('div3');require('./drag.js').drag(oDiv3);

oInput.onclick =function() {

oDiv1.style.display ='block';require('./scale.js').scale(oDiv1,oDiv2);require.async('./scale.js',function(ex) {

ex.scale(oDiv1,oDiv2);

})

}

});

B同事

//B同事寫的drag.js:define(function(require,exports,module){functiondrag(obj){vardisX =0;vardisY =0;

obj.onmousedown =function(ev){varev = ev ||window.event;

disX = ev.clientX - obj.offsetLeft;

disY = ev.clientY - obj.offsetTop;document.onmousemove =function(ev){varev = ev ||window.event;varL =require('./range.js').range(ev.clientX - disX ,document.documentElement.clientWidth - obj.offsetWidth ,0);varT =require('./range.js').range(ev.clientY - disY ,document.documentElement.clientHeight - obj.offsetHeight ,0);

obj.style.left = L +'px';

obj.style.top = T +'px';

};document.onmouseup =function(){document.onmousemove =null;document.onmouseup =null;

};returnfalse;

};

}

exports.drag = drag;//對外提供接口});

C同事

//C同事寫的scale.js:define(function(require,exports,module){functionscale(obj1,obj2){vardisX =0;vardisY =0;vardisW =0;vardisH =0;

obj2.onmousedown =function(ev){varev = ev ||window.event;

disX = ev.clientX;

disY = ev.clientY;

disW = obj1.offsetWidth;

disH = obj1.offsetHeight;document.onmousemove =function(ev){varev = ev ||window.event;varW =require('./range.js').range(ev.clientX - disX + disW ,500,100);varH =require('./range.js').range(ev.clientY - disY + disH ,500,100);

obj1.style.width = W +'px';

obj1.style.height = H +'px';

};document.onmouseup =function(){document.onmousemove =null;document.onmouseup =null;

};returnfalse;

};

}

exports.scale = scale;

});

D同事

// D同事的range.js--限定拖拽范圍

define(function(require,exports,module){functionrange(iNum,iMax,iMin){if( iNum > iMax ){returniMax;

}elseif( iNum < iMin ){returniMin;

}else{returniNum;

}

}

exports.range=range;

});

requirejs開發實例

require.config是用來定義別名的,在paths屬性下配置別名。然后通過requirejs(參數一,參數二);參數一是數組,傳入我們需要引用的模塊名,第二個參數是個回調函數,回調函數傳入一個變量,代替剛才所引入的模塊。

main.js文件

//別名配置requirejs.config({

paths: {

jquery:'jquery.min'//可以省略.js}

});//引入模塊,用變量$表示jquery模塊requirejs(['jquery'],function($) {

$('body').css('background-color','red');

});

引入模塊也可以只寫require()。requirejs通過define()定義模塊,定義的參數上同。在此模塊內的方法和變量外部是無法訪問的,只有通過return返回才行.

define 模塊

define(['jquery'],function($) {//引入jQuery模塊return{

add:function(x,y){returnx + y;

}

};

});

將該模塊命名為math.js保存。

main.js引入模塊方法

require(['jquery','math'],function($,math) {console.log(math.add(10,100));//110});

沒有依賴

如果定義的模塊不依賴其他模塊,則可以:

define(function() {return{name:"trigkit4",

age:"21"}

});

AMD推薦的風格通過返回一個對象做為模塊對象,CommonJS的風格通過對module.exports或exports的屬性賦值來達到暴露模塊對象的目的

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容