用JQuery異步實現(xiàn)順序加載外部腳本
乍看之下是個矛盾的命題,其實并不矛盾。
異步是什么,大家都知道就不說了,說說同步順序加載,也很簡單。就是假如你有A,B,C三資源需要延遲加載,但又要保證A,B,C之間能順序加載,同時不阻塞瀏覽器。
比如:我們需要加載a,b,c三個js 但是C依賴B,B依賴A,就是說在沒有A沒有加載完的前提下,如果B中使用了A中的資源那肯定是會出錯的。C也是如此。這就有了同步順序加載的概念,固使用一般的 (例子都為jQuery,其他AJAX框架應(yīng)該差別不大)
$.ajax(A);
$.ajax(B);
$.ajax(C); 這樣并不能保障ABC會順序加載完,這樣先加載完的會先執(zhí)行。
這樣的解決辦法很多比如直接<script scr="A.js"></script><script scr="B.js" ></script>。。。這樣寫在head或者body中,在IE中這是順序加載的,其他瀏覽器不知道是否這樣,但這會引出一個問題,會阻塞瀏覽器,比如我們打開一個網(wǎng)頁經(jīng)??匆姲灼梁芫貌懦霈F(xiàn)網(wǎng)頁內(nèi)容,這就是在頭部加載了過多的信息,比如CSS,javascript,也許我們可以把<script src=""> 這個方法body標(biāo)簽最下方。這樣可以讓頁面快速呈現(xiàn),但當(dāng)瀏覽器解釋到<script src=""> 時,將依然阻塞瀏覽器,就是感覺卡住的說。
這時需求就出來了,我們希望瀏覽器不被阻塞,那想到的肯定是異步加載資源。我們可以使用jQuery()的異步加載,$.ajax(options),其中有一個選項是async詢問你是否異步,默認是異步,如果你設(shè)置為false 則同步執(zhí)行,但這并不能解決問題,依然阻塞瀏覽器。
很多童鞋應(yīng)該都想到了那我們就用回調(diào)函數(shù)來實現(xiàn)同步順序加載,也就是這樣的形式 :
$.ajax(A,function(){
$.ajax(B,function(){
$.ajax(C,function(){
.......
});
});
});
哦,問題出來了,太難看了。你需要順序加載的資源過多,而且每加載完一個資源需要執(zhí)行一些處理方法,那么只要這些資源大概上了10個,那么這個縮進就灰常難看了,而且如果你開發(fā)中期需求改變,需要換加一個資源或者刪一個資源就悲劇了,眼睛都看花,而且地球人都知道javascript出錯,是最悲劇的,那個找錯誤啊。
這個問題出自我現(xiàn)在正在寫的一個個人項目,全憑興趣而作,下面給出我的解決方案:
function createLoadComponent() {
return {
textContainer : null,
setContainer: function(container){this.textContainer=container;},
println : function(data) {
this.textContainer.text("開始加載" + data + "...");
},
dataURLs : new Array(),
datasCount : 0,
count:0,
traverse : function(obj) {
if (!obj)
obj = this;
var dataURL = obj.dataURLs[this.datasCount];
if (!dataURL)
{
for(var i=0;i<obj.onCompletes.length;i+=1)
obj.onCompletes[i]();
this.clear(obj);
return;
}
if (dataURL.name&&obj.textContainer)
obj.println(dataURL.name);
obj.datasCount += 1;
$.ajax( {
url : dataURL.url,
dataType : dataURL.type ? dataURL.type : "script",
success : function(data, textStatus) {
if (dataURL.callBack)
dataURL.callBack(data);
obj.traverse(obj);
}
});
},
eventCount:0,
ready:function(callBack){
this.onCompletes[this.eventCount]=callBack;
this.eventCount+=1;
},
onCompletes:new Array(),
addURL:function(_url,_name,_type,_callBack)
{
this.dataURLs[this.count]={name:_name,url:_url,type:_type,callBack:_callBack};
this.count+=1;
return this;
},
clear : function(obj) {
if(obj)
obj=this;
obj==null;
}
}
}
這是個異步順序加載器(姑且就這么叫吧)看個使用例子:
var loadComponent = createLoadComponent();
loadComponent.addURL("/main.jsp","主頁面","html",function(data) {$(document.body).append(data);});
loadComponent.addURL("/js/comopent/tooltip/jquery.bgiframe.js","標(biāo)簽提示組件");
loadComponent.addURL("/js/comopent/tooltip/jquery.dimensions.js");
loadComponent.addURL("/js/comopent/tooltip/jquery.tooltip.min.js");
loadComponent.addURL("/js/comopent/jquery.validate.js","驗證組件");
loadComponent.addURL("/js/utils/messages_cn.js");
loadComponent.addURL("/js/comopent/jquery.cookie.js","cookie組件");
loadComponent.addURL("/js/comopent/jquery.lazyload.js", "延遲加載組件");
loadComponent.addURL("/js/comopent/jquery.simplemodal.js","模式框組件");
loadComponent.addURL("/js/comopent/jquery.quickflip.min.js","Flip組件");
loadComponent.addURL("/js/mode/UserEntity.js","用戶實體");
loadComponent.addURL("/js/controller/registController.js","注冊控制器");
loadComponent.addURL("/js/controller/loginController.js","登錄控制器");
loadComponent.addURL("/js/controller/errorController.js","錯誤控制器");
loadComponent.setContainer($("#loadtext"));
loadComponent.ready(initMain);
loadComponent.traverse();
下面說下LoadComponent中的方法:
setContainer(container) 加載信息輸出容器,不設(shè)置則不輸出,這里的參數(shù)為JQuery對象,請在執(zhí)行traverse()之前調(diào)用。
addURL(url,name,type,callBack)添加一個需加載的腳本,只有URL為必要參數(shù);name為添加的資源名字,如果你設(shè)置了輸出容器,則這個名字會在容器中顯示,type為資源類型 html, json,xml,script,由于自己項目原因我這里設(shè)置了默認值為script,callBack這里的回調(diào)方法是加載完該URL所調(diào)用的方法。
ready(callBack) 完成加載所有腳本后所執(zhí)行的回調(diào)方法,如上述initMain就會在所有資源加載完后執(zhí)行,可多次使用ready(callBack)添加多個回調(diào)方法,按添加順序執(zhí)行,請在執(zhí)行traverse()之前調(diào)用。
traverse() 開始同步加載添加的所有URL。執(zhí)行此方法后,會自動執(zhí)行clear() 方法,釋放內(nèi)存,所以,不要在執(zhí)行完traverse()方法后繼續(xù)使用該對象,應(yīng)從新create一個新對象。
注:LoadComponent是基于jquery寫的,所以之前還是需要先讓瀏覽器把jquery加載完,但jquery體積還算是比較小的,無所謂了。
---------------------------------------
其實,你寫的這個方法效率還是不夠高。因為它是順序請求的,而不是并發(fā)請求。
比如要加載a, b, c這3個JS文件,你的做法是a請求完成了再請求b,…… 相當(dāng)于總的時間中還包含了3次http連接建立的時間。
其實要保證運行順序,可以3個文件一起請求。等到都請求完畢后再按順序合并a, b, c,進行執(zhí)行??尚械淖龇ㄖ痪褪窃诿總€文件下載完的回調(diào)函數(shù)里去給一個全局變量加上此文件已加載完的標(biāo)記,同時判斷是否已經(jīng)全部加載完,完了就再按順序?qū)懭肽矰OM對象的innerHTML。
或者弄個更高效更簡潔的辦法:將合并這步操作放到服務(wù)器執(zhí)行,頁面只用請求一次:將a, b, c作為參數(shù)發(fā)給服務(wù)器。但它的壞處是不能緩存。
---------------------------------------
不知道有多少人了解過騰訊一個類似這樣功能的js....很好很強大的。。