讓dojo.require非同步載入小部件

孫群發表於2013-07-27

注:本文的技術實現是基於非AMD方式的。

最近用dojo開發了個系統,大約有40個widget,之前的做法是在首頁裡面一開始就通過dojo.require將這40多個小部件引入了,由於是在本機測試,所以一直沒有速度問題,後來部署到外網後發現效率太慢了,最長的一次需要30多秒才能完成頁面初始化。雖然我一開始引入了40多個widget,但是我並不是一上來就new這麼多個小部件,一開始我只用到了三四個小部件,所以只例項化了三四個。後面那37個小部件都是在使用者單擊某些按鈕進行分析完成時例項化的,假設如果使用者沒有進行分析操作,那我這37個widget就白引入了,沒有用到,而且造成頁面初始化時間較長,所以我開始想辦法優化require的機制。

我想達到的效果是:假設有一個小部件A,我想在執行var a = new A()操作之前才執行dojo.require("A"),這樣不會require那些無用的小部件的js檔案,就能最大限度的優化請求。如下所示:

dojo.provide("widgets.B");
dojo.require("widgets.BaseWidget");
dojo.declare("widgets.B",[widgets.BaseWidget],{
    templateString:dojo.cache("widgets.B","templates/B.html"),
    
    postCreate:function(){
        this.inherited(arguments);
        dojo.connect(this.finish,"onclick",this,this.createA);
    },
    
    createA:function(){
        dojo.require("widgets.A");
        var a = new widgets.A();
        a.placeAt(dojo.body());
        a.startup();
    },
    
    startup:function(){
        this.inherited(arguments);
    }
});

我想在單擊B小部件中的finish按鈕的時候執行createA方法,該方法會首先通過dojo.require("widgets.A")引入A.js,然後執行new操作。事實上這樣確實也可以執行。但是我通過Chrome Developer Tools中的Network發現,在B.js這個檔案被下載到瀏覽器中的時候就已經執行了dojo.require("widgets.A"),奇怪了,我明明寫的是在createA的函式中引入的A的啊,怎麼會提前引入了呢?後來發現dojo的require機制有這麼個特點:當把一個檔案下載到瀏覽器時,dojo會自動查詢該檔案中是否存在dojo.require()這樣的語句,只要發現有,就立馬去執行該語句去請求相應的js檔案,而不管該語句寫在檔案的頭部還是寫在某個函式內部。   但是這樣有個問題,如果在js檔案中寫入了dojo.require("A"),無論這樣程式碼出現在js檔案的哪個位置,dojo都會自己去請求A.js這個檔案,而不是等到執行

createA:function(){
        eval("(dojo.re"+"quire('widgets.A'))");
        var a = new widgets.A();
        a.placeAt(dojo.body());
        a.startup();
    }

上面我把dojo.require拆分成了dojo.re+quire,這樣dojo自身在B.js檔案中找不到完整的dojo.require字串了,也就不會一開始就去請求A.js檔案了,後來通過Chrome下的Network檢查發現確實是在執行createA的時候才去執行的dojo.require("widgets.A"),哈哈。但是我發現還是存在問題:dojo.require()是非同步方法,不是阻塞式的,所以語句eval("(dojo.re"+"quire('widgets.A'))")執行完後不會停滯,而會立即執行下面的var a = new widgets.A()操作,但是此時A.js還沒有下載到瀏覽器中,造成了windgets.A不存在,導致報錯,這可咋整呢?

為了徹底解決這個問題,我又重新想了下,思路是:還是通過eval的方式動態的去執行dojo.require請求,用setInterval每隔一段時間判斷我請求的A.js檔案是否已經下載到瀏覽器中,如果下載完成了,那麼才執行new widgets.A()操作。

全域性函式如下:

//檢查小部件是否已經載入
function checkWidgetLoaded(widgetName){
    var names = widgetName.split(".");
    var obj = window;
    for(var i=0;i<names.length;i++){
        var name = names[i];
        obj = obj[name];
        if(!obj){
            return false;
        }
    }
    var loaded = dojo.isFunction(obj);
    return loaded;
}

//按需載入小部件
function loadWidget(widgetName,callback){
    var loaded = window.checkWidgetLoaded(widgetName);
    if(loaded){
        if(dojo.isFunction(callback)){
            callback();
        }
    }
    else{
        var shelterDom = window.showLoading("Loading...");
        eval("(dojo.re"+"quire('"+widgetName+"'))");
        var sumTime = 0;
        var smallTime = 50;//每隔50毫秒就判斷js檔案是否已經引入到本地
        var maxTime = 15000;//超時時間15秒
        var handle = setInterval(dojo.hitch(this,function(shelterDom){
            var loaded = window.checkWidgetLoaded(widgetName);
            if(loaded){
                window.hideLoading(shelterDom);
                clearInterval(handle);
                if(dojo.isFunction(callback)){
                    callback();
                }
            }
            else{
                sumTime += smallTime;
                if(sumTime >= maxTime){
                    window.hideLoading(shelterDom);
                    clearInterval(handle);
                }
            }
        },shelterDom),smallTime);
    }
}

使用方法如下:

createA:function(){
        window.loadWidget("widgets.A",dojo.hitch(this,function(){
            var a = new widgets.A();
            a.placeAt(dojo.body());
            a.startup();
        }));
    }
至此,我們就完成了按需請求小部件js的載入機制,這樣可以大大減少在系統一開始引入的小部件的數量。

相關文章