Requirejs學(xué)習(xí)以及簡單實(shí)現(xiàn)

Requirejs是AMD規(guī)范的比較好的實(shí)踐,在看Requirejs之前先看下AMD的規(guī)范

AMD規(guī)范

看的是github上的中文翻譯的規(guī)范。

就是很簡單的一個(gè)define方法

  define(id?, dependencies?, factory);
  • id為這個(gè)定義的模塊的名字,如果不提供,默認(rèn)為指定的腳本的名字,如果提供了,必須是唯一的
  • 二參為依賴,就是模塊需要先執(zhí)行的模塊。如果不提供或者提供默認(rèn)的,就默認(rèn)是["require", "exports", "module"],就會掃描工廠方法的require來獲得依賴。就是CommonJS的寫法了(這個(gè)不就是CMD嗎?)
  • 三參為函數(shù),在所有的依賴被加載后會自動執(zhí)行

任何全局函數(shù)必須有一個(gè)amd屬性來標(biāo)識遵循AMD編程接口。

Requirejs

這個(gè)加載器主要解決的問題:

  • 大量JS引入頁面的時(shí)候會導(dǎo)致頁面假死,用了之后就變成異步的了
  • 還有處理各模塊之間的依賴,因?yàn)椴挥玫脑捒赡躂S的引入順序需要自己控制好

Requirejs使用

就是引一下requirejs,然后data-main指明入口文件。就像這樣:

<script src="require.js" data-main="main.js"></script>

然后我們在入口文件里面就可以申明依賴以及函數(shù)了。

//main.js
//引了一個(gè)模塊sec
requirejs(["sec"],function(sec){
   sec.action()
});
//sec.js
//這里按照commonjs的形式讓requirejs自己去搜一遍,再引入third.js
define(function (require, exports, module) {
    var third =require('third');
    third();
    module.exports.action = function () {console.log(22)};
});
//third.js
define(function (require, exports, module) {
    module.exports = function () {console.log(11)};
});

入口文件里寫requirejs或者define其實(shí)都行的。只是requirejs顯示是在入口文件。這一這里的sec.js等等都是異步加載的,不會導(dǎo)致頁面死在那里。

文件的路徑問題

我們可以在主入口js中使用requirejs.config來進(jìn)行配置。

requirejs.config({
   baseUrl: "js/lib",
  paths: {
    "sec": "sec.min"
  }
})

我們可以配置基本的url,可以針對特別的模塊單獨(dú)定義路徑,這里的路徑還可以是網(wǎng)絡(luò)請求的位置。

加載非amd規(guī)范的的模塊

我們引入的模塊都必須是define申明好的,如果不是的話,得在require里面申明一下。exports為對外輸出的變量

   require.config({
    shim: {
      'underscore':{
        exports: '_'
      },
      'backbone': {
        deps: ['underscore', 'jquery'],
        exports: 'Backbone'
      }
    }
  });

require.js的插件

提供了domready這種類似的插件

require(['domready!'], function (doc){
    // called once the DOM is ready
});

寫一個(gè)require

在閱讀源碼之前,先試著實(shí)現(xiàn)一把感覺這樣子看源碼的實(shí)現(xiàn)會學(xué)到更多。鏈接

先實(shí)現(xiàn)data-main的主入口,很簡單就是分析整個(gè)頁面,得到data-main,然后新建了一個(gè)script,來異步加載js。

var scripts = document.getElementsByTagName('script');
var sLength = scripts.length;
var mainJs;
for(var i=0;i<sLength;i++){
    var name = scripts[i].getAttribute("data-main");
    if(name){
        mainJs = name;
    }
}
var mainScript = document.createElement('script');
mainScript.src = mainJs;
document.body.appendChild(mainScript);

然后就是聲明define方法,我最開始的時(shí)候使用的是onload來通知,這樣子可以解決一層的依賴。就是如果main依賴了a和b,是可以的。但是如果b依賴了c,這種方法就失敗了。

//allModule用來保存所有加載的模塊
var allModule = {};
//主要的define方法
var define = function(id,array,cb){
    var length = array.length;
    if(length > 0){
        var i = 0;
        array.forEach(function(value,index,array){
            var tempScript = document.createElement('script');
            tempScript.src = array[index]+'.js';
            //目前用的onload來監(jiān)聽加載完畢,但是如果有依賴的話,就不行了
            tempScript.onload = function(){
                i++;
                finish();
            }
            document.body.appendChild(tempScript);
        })
        function finish(){
            if(i == length){
                var modules =[];
                for(var x = 0 ; x<array.length;x++){
                    modules[x] = allModule[array[x]];
                }
                allModule[id] = cb.apply(null ,modules);
            }
        }
    }else{
        allModule[id] = cb();
    }
};

于是我不用onload來通知,選擇在不依賴其他的模塊調(diào)用完成時(shí)來進(jìn)行通知,并且引用過他的模塊嘗試執(zhí)行callback,如果這個(gè)模塊所需要的都加載完了就可以執(zhí)行。一層層的向上通知。

//allModule用來保存所有加載的模塊
var allModule = {};
//主要的define方法
function _registerModule(id,dependence,father){
    if(!allModule[id]){
        allModule[id]= {};
        var newModule = allModule[id];
        newModule.func = undefined;//模塊的結(jié)果
        newModule.dependence = dependence;//依賴的模塊
        newModule.dependenceLoadNum = 0;//已經(jīng)依賴的模塊
        newModule.finishLoad = function(){};//完成load之后觸發(fā)的方法
        if(father){
            if(newModule.referrer){
                newModule.referrer.push(father);
            }else{
                newModule.referrer = [];//被引用到的模塊
                newModule.referrer.push(father);
            }
        }
    }else{
        var newModule = allModule[id];
        if(father){
            newModule.referrer.push(father);
        }
        if(dependence){
            newModule.dependence = dependence;
            newModule.dependenceLoadNum = 0;
        }
    }
}
var define = function(id,array,cb){
    _registerModule(id,array,'',cb);
    array.forEach(function(value,index,array){
        _registerModule(array[index],[],id,cb);
    });
    var thisModule = allModule[id];
    if(array.length > 0){
        array.forEach(function(value,index,array){
            var tempScript = document.createElement('script');
            tempScript.src = array[index]+'.js';
            document.body.appendChild(tempScript);
        })
        thisModule.finishLoad = _finish;
    }else{
        thisModule.func = cb();
        _refererFinish();
    }
    function _finish(){
        thisModule.dependenceLoadNum++;
        if(thisModule.dependenceLoadNum == thisModule.dependence.length){
            var modules =[];
            for(var x = 0 ; x < array.length; x++){
                modules[x] = allModule[array[x]].func;
            }
            thisModule.func = cb.apply(null ,modules);
        }
        _refererFinish();
    }
    function _refererFinish(){
        if(thisModule.referrer){
            thisModule.referrer.forEach(function(value,index,array){
                allModule[array[index]].finishLoad();
            });
        }
    }
};

初次優(yōu)化

在勐喆的指導(dǎo)下進(jìn)行了一次簡單的邏輯優(yōu)化,因?yàn)樵诂F(xiàn)在的實(shí)現(xiàn)中,每個(gè)模塊都既存了依賴的列表,以及被依賴到的列表??梢酝ㄟ^事件監(jiān)聽的形式進(jìn)行一次解耦。也就是模塊不再需要知道誰依賴了他,通過注冊事件的形式來通知。

//allModule用來保存所有加載的模塊
var allModule = [];
function Module(id,dependence){
    this.func = undefined;
    this.dependence = dependence;
    this.dependenceLoadNum = 0;
    this.handlers = {};
}
Module.prototype={
    on: function(name,handler){
        if(!this.handlers[name]){
            this.handlers[name] = [];
            this.handlers[name].push(handler);
        }else{
            this.handlers[name].push(handler);
        }
    },
    emit:function(name){
        if(!!this.handlers[name]){
            this.handlers[name].forEach(function(value){
                value();
            })
        }
    }
}
function _registerModule(id,dependence){
    var i  = allModule.length;
    if(!allModule[id]){
        allModule[i++] = allModule[id]= new Module(id,dependence);
    }else{
        if(dependence){
            allModule[id].dependence = dependence;
            allModule[id].dependenceLoadNum = 0;
        }
    }
    dependence.forEach(function(value){
        _registerModule(value,[]);
    });
}
//cb為加載完了執(zhí)行的方法
var define = function(id,array,cb){
    //注冊相關(guān)的模塊
    _registerModule(id,array);
    var thisModule = allModule[id];
    if(array.length > 0){
        array.forEach(function(value){
            thisModule.on('finish' + value,function(){
                _finish();
                _notifyModule();
            });
            var tempScript = document.createElement('script');
            tempScript.src = value+'.js';
            document.body.appendChild(tempScript);
        })
    }else{
        thisModule.func = cb();
        _notifyModule();
    }
    function _finish(){
        thisModule.dependenceLoadNum++;
        if(thisModule.dependenceLoadNum == thisModule.dependence.length){
            var modules =[];
            array.forEach(function(value,index){
                modules[index] = allModule[value].func;
            })
            thisModule.func = cb.apply(null ,modules);
        }
    }
    //通知全局的模塊加載完畢
    function _notifyModule(){
        allModule.forEach(function(value,index,array){
            array[index].emit('finish' + id);
        });
    }
};

這樣子的話,define模塊的時(shí)候就會注冊依賴模塊的監(jiān)聽器。然后在依賴的模塊執(zhí)行完了時(shí)候就會進(jìn)行全局的觸發(fā)_notifyModule,通知每一個(gè)模塊這個(gè)模塊加載OK。于是注冊過這個(gè)事件的模塊就會觸發(fā)_finish方法。這樣子最大的好處是進(jìn)行了一次解耦,代價(jià)是會進(jìn)行全局的廣播的形式來通知。其實(shí)我們?nèi)绻胮romise來做的話,可以更好的管理狀態(tài),但是為了兼容性沒有去嘗試。

再次優(yōu)化

上面全局通知的代價(jià)有些高,看完源碼之后才發(fā)現(xiàn),其實(shí)事件可以綁定在dependence的模塊上面,這樣就不用全局通知了。而且這次的修改支持多個(gè)文件同時(shí)引用一個(gè)模塊了。

function _registerModule(id,dependence,defined){
    var i  = allModule.length;
    if(!allModule[id]){
        allModule[i++] = allModule[id]= new Module(id,dependence);
        allModule[id].defined = defined;
    }else{
        if(dependence){
            allModule[id].dependence = dependence;
            allModule[id].dependenceLoadNum = 0;
        }
    }
    dependence.forEach(function(value){
        _registerModule(value,[],false);
    });
}
//cb為加載完了執(zhí)行的方法
var define = function(id,array,cb){
    //注冊相關(guān)的模塊
    _registerModule(id,array,true);
    var thisModule = allModule[id];
    if(array.length > 0){
        array.forEach(function(value){
            allModule[value].on('finish',function(){
                _finish();
            })
            if(!(allModule[value].defined)){
                var tempScript = document.createElement('script');
                tempScript.src = value+'.js';
                document.body.appendChild(tempScript);
            }
        })
    }else{
        thisModule.func = cb();
        thisModule.emit('finish');
    }
    function _finish(){
        thisModule.dependenceLoadNum++;
        if(thisModule.dependenceLoadNum == thisModule.dependence.length){
            var modules =[];
            array.forEach(function(value,index){
                modules[index] = allModule[value].func;
            })
            thisModule.func = cb.apply(null ,modules);
            //繼續(xù)向上層觸發(fā)
            thisModule.emit('finish');
        }

    }
};

這次的優(yōu)化改了define方法,去掉了全局的通知。還增加了defined屬性來支持多個(gè)模塊引用同一個(gè)模塊。

順便給個(gè)github的傳送門,喜歡的朋友star一下啊,自己平時(shí)遇到的問題以及一下學(xué)習(xí)的思考都會在上面記錄~

參考:

http://www.ruanyifeng.com/blog/2012/11/require_js.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容