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