我們?yōu)槭裁匆K化?
一般出來什么東西,說明當(dāng)前的工具有其缺陷,或者是為了方便什么。
那么我們就來看看通過模塊,我們可以做到什么?
我們可以只關(guān)注自己的核心邏輯,而不用考慮其中的輔助功能。比如我們想要實現(xiàn)計算兩個文本框中數(shù)值的和,我們只需要關(guān)注業(yè)務(wù)邏輯,而不需要關(guān)注自己如何查找到該元素(查找到元素可以交給jquery來做,還解決了兼容性問題)。
通過模塊,我們可以方便的使用別人的代碼,需要什么模塊就引入對應(yīng)的依賴模塊。
我們先來看看幾種原生js的實現(xiàn)模塊化得方法(下面說的每一個方法都是為了解決上一種方法出現(xiàn)的問題):
-
原始的模塊寫法:
function a1(){ } function a2(){ } 缺點:污染全局變量,可能造成變量沖突,無法直觀的顯示二者的聯(lián)系
解決上一問題
var module = {
a1: function(){
},
a2: function(){
}
}
缺點:用戶可以在外面修改module內(nèi)的變量
- 解決上一問題
var module = (function(){
var m1 = function(){
};
var m2 = function(){
};
return {
m1 : m1,
m2 : m2
};
});
通過立即執(zhí)行函數(shù),這樣就解決了模塊內(nèi)部變量可能被修改的問題
缺點: 如果此模塊需要依賴另一個模塊才能生效
- 解決上一問題
var module = (function(mod){
mod.m1 = function(){
};
return mod;
})(another_module);
下面我們介紹現(xiàn)在應(yīng)用廣泛的javascript模塊規(guī)范:CommonJS,AMD,以及國內(nèi)的CMD。
- CommonJS
我們先來說CommonJS,在其中有一個require()方法,用于加載模塊
比如:
假設(shè)我們的一個模塊(add_module)中有一個求兩數(shù)之和的方法,在另一個地方我們就可以通過下面的方法使用這個求和方法。
var addModule = require(‘a(chǎn)dd_module’);
addModule.add(1,2);
- AMD
既然已經(jīng)有了CommonJS,為什么還會出現(xiàn)AMD呢?
因為CommonJS不適合瀏覽器環(huán)境,上面的代碼假如在瀏覽器中執(zhí)行,那么瀏覽器需要時間來加載add_module,如果網(wǎng)絡(luò)條件不好,瀏覽器會出現(xiàn)“假死”狀態(tài)。因此瀏覽器端不能采用“同步加載”,而應(yīng)該使用“異步加載”。
AMD(異步模塊定義),采用異步的方式加載模塊,所有需要依賴其他模塊的語句都被定義在回調(diào)函數(shù)中。
用AMD的語法改寫上面CommonJS的寫法:
require([‘a(chǎn)dd_module’],function(addModule){
addModule.add(1,2);
});
- 基于AMD規(guī)范的javascript庫:require.js
require.js是為了實現(xiàn)下面的兩個效果:
- 實現(xiàn)js文件的異步加載,避免網(wǎng)頁失去響應(yīng);
- 管理模塊之間的依賴性,便于代碼的編寫和維護。
3.1 使用require.js
從官網(wǎng)下載require.js,在html中引入
<script src="js/require.js" defer async="true" ></script>
async屬性表明這個文件需要異步加載,避免網(wǎng)頁失去響應(yīng)。IE不支持這個屬性,只支持defer,所以把defer也寫上。
引入我們自己寫的需要執(zhí)行的js文件
<script src="js/require.js" data-main=“main” defer async="true" ></script>
data-main屬性的作用是,指定網(wǎng)頁程序的主模塊。在上例中,就是js目錄下面的main.js,這個文件會第一個被require.js加載。由于require.js默認的文件后綴名是js,所以可以把main.js簡寫成main。
main.js文件:
require(['moduleA', 'moduleB'], function (moduleA, moduleB){
// some code here
});
require()函數(shù)接受兩個參數(shù)。第一個參數(shù)是一個數(shù)組,表示所依賴的模塊,上例就是['moduleA', 'moduleB'],即主模塊依賴這2個模塊;第二個參數(shù)是一個回調(diào)函數(shù),當(dāng)前面指定的模塊都加載成功后,它將被調(diào)用。加載的模塊會以參數(shù)形式傳入該函數(shù),從而在回調(diào)函數(shù)內(nèi)部就可以使用這些模塊。
require()異步加載moduleA,moduleB,瀏覽器不會失去響應(yīng);它指定的回調(diào)函數(shù),只有前面的模塊都加載成功后,才會運行,解決了依賴性的問題。
比如我們需要依賴jquery
require(['jquery'], function ($){
// some code here
});
主模塊的依賴模塊是['jquery']。
默認情況下,require.js假定這個模塊與main.js在同一個目錄,文件名為jquery.js,然后自動加載。
使用require.config()方法,我們可以對模塊的加載行為進行自定義。require.config()就寫在主模塊(main.js)的頭部。
參數(shù)就是一個對象,這個對象的paths屬性指定各個模塊的加載路徑。
如果我們的依賴文件和main.js不在同一文件夾下呢?
require.config({
paths: {
"jquery": "lib/jquery.min"
}
});
也可以通過配置baseUrl來改變基目錄
require.config({
baseUrl: "js/lib",
paths: {
"jquery": "jquery.min"
}
});
自定義的require.js加載的模塊,采用AMD規(guī)范,即就是模塊必須采用特定的define()函數(shù)來定義。
- 如果一個模塊不依賴其他模塊,那么可以直接定義在define()函數(shù)之中。
define(function (){
var add = function (x,y){
return x+y;
};
return {
add: add
};
});
- 如果這個模塊還依賴其他模塊,那么define()函數(shù)的第一個參數(shù),必須是一個數(shù)組,指明該模塊的依賴性。
define(['myLib'], function(myLib){
function foo(){
myLib.doSomething();
}
return {
foo : foo
};
});
加載非規(guī)范的模塊
理論上,require.js加載的模塊,必須是按照AMD規(guī)范、用define()函數(shù)定義的模塊。但是實際上,雖然已經(jīng)有一部分流行的函數(shù)庫(比如jQuery)符合AMD規(guī)范,更多的庫并不符合。那么,require.js是否能夠加載非規(guī)范的模塊呢?
回答是可以的。
這樣的模塊在用require()加載之前,要先用require.config()方法,定義它們的一些特征。
舉例來說,underscore和backbone這兩個庫,都沒有采用AMD規(guī)范編寫。如果要加載它們的話,必須先定義它們的特征。
require.config({
shim: {
'underscore':{
exports: '_'
},
'backbone': {
deps: ['underscore', 'jquery'],
exports: 'Backbone'
}
}
});
require.config()接受一個配置對象,這個對象除了有前面說過的paths屬性之外,還有一個shim屬性,專門用來配置不兼容的模塊。具體來說,每個模塊要定義(1)exports值(輸出的變量名),表明這個模塊外部調(diào)用時的名稱;(2)deps數(shù)組,表明該模塊的依賴性。
4.CMD
關(guān)于CMD的介紹,大家可以點擊這里查看;
關(guān)于AMD和CMD的比較,大家可以點擊這里看玉伯寫的一篇評論;
SeaJS和RequireJS的區(qū)別,大家可以點擊這里。
本文內(nèi)容是我自己在阮一峰博客的基礎(chǔ)上整理的,原文大家可以查看:
http://www.ruanyifeng.com/blog/2012/10/javascript_module.html
http://www.ruanyifeng.com/blog/2012/10/asynchronous_module_definition.html
http://www.ruanyifeng.com/blog/2012/11/require_js.html