模仿RequireJs的用法寫一個低配版的模塊加載器
Contents
- 前言
- 回顧RequireJs的基本用法
- 實現(xiàn)原理
- 使用方法
- 總結
前言
前段時間一直想用單頁開發(fā)技術寫一個自己的個人網(wǎng)站(使用es2015),寫了一部分之后,發(fā)現(xiàn)單頁應用因為只有一個頁面,所以第一次加載index.html時就要下載所有js文件暖眼,并且為了好管理各個部分的狀態(tài),需要劃分頁面的各個功能區(qū)為各個模塊颜凯,es2015本身是不支持一些模塊規(guī)范的(比如AMD湿颅、CMD叨襟、CommonJs等)敬扛,所以只能這樣模擬實現(xiàn):
// global
var spa = (function(){...})();
// module blog
spa.blog = (function(){
...
return {
do1: do1,
do2: do2,
};
})();
// module model
spa.model = (function(){...})();
// module shell
spa.model = (function(){...})();
并且各個模塊之間又存在一些依賴關系讥巡,在index.html里面寫script標簽來載入模塊時需要寫很多個,同時也要根據(jù)依賴關系來確定書寫順序舔哪,頁面邏輯混亂,如下:
<script type="text/javascript" src="/javascripts/spa.utils.js"></script>
<script type="text/javascript" src="/javascripts/spa.model.js"></script>
<script type="text/javascript" src="/javascripts/spa.mock.js"></script>
<script type="text/javascript" src="/javascripts/spa.chat.js"></script>
<script type="text/javascript" src="/javascripts/spa.blog.js"></script>
<script type="text/javascript" src="/javascripts/spa.action.js"></script>
<script type="text/javascript" src="/javascripts/spa.shell.js"></script>
之前用過RequireJs(一個流行的JavaScript模塊加載器)槽棍,它是用同構js的架構來寫的捉蚤,所以node.js環(huán)境下也能使用。我想自己可以嘗試一下寫一個低配版的js模塊加載器 requireJs-nojsja 來應付一下我這個單頁網(wǎng)站炼七,當然只是大致模仿了主要功能缆巧。
回顧RequireJs的基本用法
- 配置模塊信息
requirejs.config({
baseUrl: '/javascripts', // 配置根目錄
paths: {
moduleA: 'a.js',
moduleB: 'b.js',
moduleC: 'c.js',
},
shim: { // 配置不遵循amd規(guī)范的模塊
moduleC: {
exports: 'log',
deps: ['moduleA']
}
},
});
- 定義一個模塊
define(name, ['moduleA', 'moduleB'], function(a, b){
...
return {
do: function() {
a.doSomething();
b.doAnother();
}
};
});
- 引用一個模塊
// 引用模塊
require(['moduleA', 'moduleB'], function(a, b) {
a.doSomething();
b.doAnother();
});
實現(xiàn)原理
- config方法確定各個模塊的依賴關系
/* 記錄模塊訪問地址和模塊的依賴等信息 */
Require.config({
baseUrl: '/javascripts/',
paths: {
'moduleA': './moduleA.js', // 相對于當前目錄
'moduleB': '/javascripts/moduleB.js', // 不使用baseUrl
'moduleC': 'moduleC.js',
'moduleD': {
url: 'moduleD.js',
deps: ['moduleE', 'moduleF'],
},
...
},
shim: {
'moduleH': {
url: 'moduleH.js',
exports: 'log',
},
}
});
- 數(shù)據(jù)請求過程分析
(1) config配置模塊信息時并不會觸發(fā)網(wǎng)絡請求
(2) 在index.js主入口文件里使用require方法引用多個模塊時,根據(jù)config配置文件構造一下所有模塊的依賴分析樹豌拙。按深度優(yōu)先或是廣度優(yōu)先來遍歷這個依賴樹陕悬,將所有依賴按照依賴順序放進一個數(shù)組,最后進行數(shù)組去重處理按傅,因為會出現(xiàn)依賴重復的情況
var dependsTree = new Tree('dependsTree');
var dependsArray = [];
var dependsFlag = {}; // 解決循環(huán)依賴
// 創(chuàng)建樹
setDepends(depends, dependsTree);
// 得到依賴數(shù)組
sortDepends(dependsArray, dependsTree);
// 數(shù)據(jù)去重
arrayFilter(dependsArray);
return dependsArray;
(3) 創(chuàng)建XHR對象異步下載數(shù)組里面的所有js文件捉超,按照依賴順序挨個解析js代碼胧卤,解析完成后觸發(fā)回調函數(shù),回調函數(shù)里傳入各個模塊的引用
// ajax下載代碼文件
Utils.request(url, 'get', null, function(responseText){
// 暫時保存
_temp[module_name] = responseText;
});
// 文件下載完成后eval解析代碼
array.map(function(jsText){
...
eval(jsText);
...
});
// 調用回調函數(shù)
callback.apply(null, [dep1, dep2, dep3]);
使用方法
詳細說明: github README.md
總結
- 下載js代碼時我用了ajax來實現(xiàn)拼岳,所以對于跨域文件和CDN會有點問題枝誊,這個可以改成創(chuàng)建script標簽,指定標簽src惜纸,最后將document.head.appendChild(script)叶撒,這樣來解決,其它的諸如使用XMLHttpRequest 2.0耐版,iframe等也可以的祠够,可以實驗一下。
- 解析代碼時我用了eval的方法粪牲,這個eval在JavaScript里面是眾說紛紜古瓤,可以看看這個,如果是用了上面創(chuàng)建script標簽的方法的話虑瀑,就不用自己eval了湿滓。
- 發(fā)現(xiàn)一個bug,存在循環(huán)依賴時舌狗,代碼會報錯叽奥,還沒去解決。RequireJs是這樣處理的:模塊a依賴b痛侍,同時b依賴a朝氓,這種情況下b的模塊函數(shù)被調用時,被傳入的a是undefined主届,所以需要自己在b里面手動require一下a赵哲。