傳統的ajax寫法:
$.ajax({ url:"1.json", type:"get", success:function(data){}, error:function(){} });
jquery 1.7以后的新寫法,
$.ajax({ url:"1.json", type:"get" }).done(function(data){ }).fail(function(){ });
我就納悶了.$.ajax()返回的是XMLHttpRequest對象.
我們都知道XMLHttpRequest是ajax的一個核心對象,用於和服務器交互的,
可是XMLHttpRequest對象根本就沒有什么done,fail方法,
這里的方法是怎么加上去的呢?
我們從done入手.在官網api上搜索done.
發現一個Deferred關鍵詞.
我們現在就從jQuery的Deferred開始講起吧.
Deferred對象是jquery開發團隊設計的,為了增強jquery的回調功能,在1.7版本中加入了jquery.
defer英語意思是延遲的意思.那么他是如何延遲的呢?
首先來理解一下基本知識,要不然后面沒法說了.
$.Deferred([fun])
$.Deferred()沒有參數時 返回Deferred對象;
有參數時,表示在這個參數上做延遲或異步操作,並且返回Deferred對象.
done(fn) 當延遲成功后調用該方法
fail(fn) 當延遲失敗時調用失敗
then(done,fail) 這個是done和fail的總寫方式
always(fn) 不管延遲執行的成功還是失敗,都執行的方法
上面的fn可能是一個function,也有可能是多個以逗號分隔的function函數
resolve和reject方法一旦執行,表示開始執行done,fail,then,always方法,
注意Deferred對象可以一次掛接多個done,fail方法,按照你分布的順序依次執行.
resolve(value) 告訴對象執行done回調,value是參數
reject(value) 告訴對象執行fail回調,value是參數.
調用done回調代碼:
var dfd = $.Deferred(); dfd.done(function(value) { alert(value); });
dfd.resolve("執行done回調");
調用fail回調代碼:
var dfd = $.Deferred(); dfd.done(function(value) { alert(value); }); dfd.reject("執行fail回調");
調用then回調代碼:
var dfd = $.Deferred(); var doneFun=function(value) { alert("done:"+value); }; var failFun=function(value) { alert("fail:"+value); } dfd.then(doneFun,failFun); dfd.reject("思思博士");
調用always回調代碼:
var dfd = $.Deferred(); var doneFun = function(value) { alert("done:" + value); }; var failFun = function(value) { alert("fail:" + value); } dfd.then(doneFun, failFun).always(function() { alert("不管你延遲成功還是失敗,我都要執行always."); }); dfd.resolve("我要執行done,但是這不影響我執行always!");
state() 返回deferred對象目前的狀態
1.pending:操作沒有完成
2.resolved:操作成功
3.rejected:操作失敗
代碼:
var dfd=$.Deferred(); dfd.done(function(){ console.log("done"); }); console.log(dfd.state()); dfd.resolve(); console.log(dfd.state());
上面我們說了:resolve表示立馬執行回調,
所以這個地方的彈出是這樣的
黃金搭檔 progress(fn)和notify(value)
progress和done之類的方法截然不同.
progress(fn)的fn表示一個回調函數,這個回調函數由notify()方法通知執行.
代碼:
var dfd=$.Deferred(); dfd.progress(function(data){ alert(data); }).done(function(data){ alert("done:>>>>>"+data); }).fail(function(data){ alert("fail:>>>>"+data); }); function getProcess(){ dfd.notify("我是progress回調函數的參數"); var a=true; //下面判斷是為了執行done還是fail if(a){ dfd.resolve("執行done....."); }else{ dfd.reject("執行fail......"); } }
<input type="button" value="確定" onclick="getProcess()" />
根據以上分析,Deferred對象才有done這樣的反法,
根據$.ajax().done()推測$.ajax()返回的應該是Deferred對象?
姑且認為這個結論是對的吧.
那么jQuey除了$.ajax()有done方法外,還有哪些東西有done方法.
$.when(d);d是一個或多個延遲對象,或者普通的JavaScript對象。
下面看一下參數是一個普通對象的.
$.when({address:"china",sex:"男"}) .done(function(data){ console.log(data.address) });
這個例子主要是為了演示參數的傳遞的
前面說了同一延遲的多個回調的兩種寫法
看下面:
1.
$.ajax("1.json") .done(function(data){ alert("done1") }).done(function(){ alert("done2"); });
2.
$.ajax("1.json") .done(function(data){ alert("done1") },function(){ alert("done2"); });
結合when用法
$.when($.ajax("1.json")) .done(function(data){ console.log(data); });
現在說一說多個延遲的同一個調用
$.when($.ajax("1.json"), $.ajax("2.json"),$.ajax("3.json")) .done(function(data1, data2,data3) { console.log(data1); console.log(data2); console.log(data3); }).fail(function(data1,data2,data3){ console.log(data1); console.log(data2); console.log(data3); });
這個地方返回值有點臃腫,他把$.ajax()對象給返回回來了
這個地方的用的是ajax做的延遲,
那么我們能不能用setTimeout模擬延遲
function delay(){ setTimeout(function(){ alert("執行啦"); },2000); } $.when(delay()) .done(function(){ alert("done"); });
不對,這個地方首先執行的是done,2s后執行的才是setTimeout.
思考思考,想一想上面的參數.
這個地方最多只是將delay()當做參數傳遞給done回調函數的參數.因為不是一個Deferred類型的參數.
因為$.ajax()返回Deferred類型才能使用done一樣.
那么要怎么樣才能讓delay返回一個Deferred對象來完成我們的延遲模擬呢.
現在讓我們回想一下上面的第一串代碼$.Deferred();
這個可以返回Deferred類型.
改寫上面代碼:
var dfd=$.Deferred(); function delay(dfd){ var bool=true; setTimeout(function(){ alert("delay的setTimeout執行啦!"); if(bool){ dfd.resolve("done"); }else{ dfd.reject("fail"); } },2000); return dfd; } $.when(delay(dfd)) .done(function(value){ alert(value); }).fail(function(value){ alert(value); });
這個時候我們終於實現了延遲,回到功能.
上面說了 done只要一遇到resolve或fail遇到reject就會立即執行,
那么我們在底部添加一行代碼:
修改上面的代碼:
var dfd=$.Deferred(); function delay(dfd){ var bool=true; setTimeout(function(){ console.log("delay的setTimeout執行啦!"); if(bool){ dfd.resolve("done"); }else{ dfd.reject("fail"); } },2000); return dfd; } $.when(delay(dfd)) .done(function(value){ console.log("done1"+value); }).fail(function(value){ console.log("fail1"+value); }); dfd.resolve("我是來搗亂的....");
順序變成了這樣:
很明顯破壞了我們模擬延遲的目的.
如何解決
將dfd從全局改成局部變量,這樣別人就不能輕易的改變狀態了.
function delay(){ var dfd=$.Deferred(); var bool=true; setTimeout(function(){ alert("delay的setTimeout執行啦!"); if(bool){ dfd.resolve("done"); }else{ dfd.reject("fail"); } },2000); return dfd; } $.when(delay()) .done(function(value){ alert("done1"+value); }).fail(function(value){ alert("fail1"+value); });
還是那句話:done只要一遇到resolve或fail遇到reject就會立即執行
改寫上面的代碼:
function delay(){ var dfd=$.Deferred(); var bool=true; setTimeout(function(){ console.log("delay的setTimeout執行啦!"); if(bool){ dfd.resolve("done"); }else{ dfd.reject("fail"); } },2000); return dfd; } var delay2=delay(); delay2.resolve(); $.when(delay()) .done(function(value){ console.log("done1"+value); }).fail(function(value){ console.log("fail1"+value); });
這個時候執行變成這樣,2s后:
如果我們的Deferred可以這樣任意的被篡改,那么我們的程序健壯何在,
有沒有一個辦法讓外人無法訪問到Deferred對象,但是又不影響我們的回調函數的調用.
答案是有:promise();
改寫代碼:
function delay(){ var dfd=$.Deferred(); var bool=true; setTimeout(function(){ console.log("delay的setTimeout執行啦!"); if(bool){ dfd.resolve("done"); }else{ dfd.reject("fail"); } },2000); return dfd.promise(); } //var delay2=delay(); //delay2.resolve(); $.when(delay()) .done(function(value){ console.log("done1"+value); }).fail(function(value){ console.log("fail1"+value); });
這個時候你在把注釋放開,就會報錯了.
在看下面這串代碼的報錯:
$.ajax().resolve();
是不是很熟悉的報錯.
所以我們的$.ajax()更確切的說最終返回的是Promise對象.
$.ajax(),$.when()說白了不過是一個方法而已.
既然jQuery能夠搞個$.ajax(),$.when()接口出來,我們理所當然的也能搞一個
這樣的接口出來了.
回到最上面的那串代碼:
$.Deferred(fn);
我們一直都是在用無參的$.Deferred();
那這個fn參數是干嘛的呢?fn主要用來部署異步或延遲操作的.
$.delay=$.Deferred(function(dfd){ setTimeout(function(){ alert("setTimeout執行啦!!!!"); dfd.resolve(); },2000); }); $.delay.done(function(){ alert("done1"); });
注意:這里的dfd不需要我們調用時傳遞,自己會傳一個Deferred對象進去
這樣看上去是不是就和$.ajax()和$.when()差不多了.
但是又不太一樣,$.ajax()和$.when()可以傳參數我們的$.delay不能傳遞參數,他也不是一個方法,就是一個Promise對象.
繼續優化上面的代碼,使之可以傳遞參數:
$.delay = function(time) { return $.Deferred(function(dfd) { setTimeout(function() { alert("setTimeout執行啦!!!!"); dfd.resolve(); }, time); }) } $.delay(5000).done(function() { alert("done1"); });
代碼優化好了之后,功能實現了;
此時問題又來了.$.delay()是jQuery已經定義過的一個方法,我們定義的方法額jQuery重名了
不如我們吧$.delay()改成一個普通的方法,不更好.
var delay = function(time) { return $.Deferred(function(dfd) { setTimeout(function() { alert("setTimeout執行啦!!!!"); dfd.resolve(); }, time); }) } delay(5000).done(function() { alert("done1"); });
或者是這樣:
function delay(time) { return $.Deferred(function(dfd) { setTimeout(function() { alert("setTimeout執行啦!!!!"); dfd.resolve(); }, time); }) } delay(5000).done(function() { alert("done1"); });
上面說的是通過返回Promise對象使其有了done等接口,
能否直接在給出的函數上布置接口呢?這里就用到了我們之前用到的promise()
代碼如下:
var dfd=$.Deferred(); function delay(d,time) { setTimeout(function(){ alert("setTimeout執行啦!!"); d.resolve(); },time); } dfd.promise(delay); delay.done(function() { alert("done1"); }); delay(dfd,5000);
這里又出現了dfd這樣的全局變量,而且在調用時還是需要傳遞dfd參數,似乎有點不好看,優化一下:
function ansy(time) { var dfd = $.Deferred(); function delay(d, time) { setTimeout(function() { alert("setTimeout執行啦!!"); d.resolve("我被done彈出來了"); }, time); } dfd.promise(delay); delay(dfd,time); return delay; } ansy(2000).done(function(value){ alert("done說:"+value); });
好了總算說完了.
現在我們看看jQuery1.7.2的ajax源碼:
為了閱讀方便,我把源碼刪掉了一部分,行號和真實的jquery行號對不上.
ajax最終返回的是jqXHR,而在7424行在bei部署成了Promise對象.
因此一開始的疑惑終於解決了,$.ajax()返回的是Promise,並非我們在官網上看到的是XMLHttpRequest對象.