jQuery 原始碼解析程式碼及更多學習乾貨: 猛戳GitHub
本篇程式碼為 my-jQuery 1.0.4.js
建議閱讀本篇先弄懂上一篇Callbacks 原理分析,因為Deferred非同步回撥是基於Callbacks。下載原始碼然後根據文章思路學習,最好自己邊思考邊多敲幾遍。
一、基本概念
Promise/A+規範
首先推薦各位閱讀一下 Promise/A+規範
Promise作為一個模型,提供了一個在軟體工程中描述的延時概念的解決方案。
- 1.Promise表示一個非同步操作的最終結果
- 2.與Promise最主要的互動方法是,通過將函式傳入他的then方法,從而獲得Promise最終的值或Promise最終拒絕的值的原因。
- 3.一個Promise必須處於以下幾個狀態之中:pending,fulfilled,rejected.
- 4.一個Promise必須提供一個then方法來獲取其值或原因。
Promise和Deferred的關係
Promise是ES6提出的非同步程式設計模式,可以參考阮一峰ES6- Promise
Deferred是jQuery提出的回撥函式解決方案,主要依賴Callbacks回撥,可以參考上一篇Callbacks原理解析。
主要解決的問題是:當一個非同步依賴於另一個非同步請求的結果時,或者某個操作需要等另外幾個操作都結束後才開始等,更強大的是從ajax操作擴充套件到了所有操作,動畫、定時中也常用。
二、開始剖析 Deferred
1.Deferred 提供的API
(1) $.Deferred()
生成一個deferred物件。
(2)deferred.done()
指定操作成功時的回撥函式
(3) deferred.fail()
指定操作失敗時的回撥函式
(4) .promise()
返回一個Promise物件來觀察當某種型別的所有行動繫結到結合,排隊與否還是已經完成。
(5) deferred.resolve()
手動改變deferred物件的執行狀態為"已完成",從而立即觸發done()
方法。
(6)deferred.reject()
這個方法與deferred.resolve()
正好相反,呼叫後將deferred物件的執行狀態變為"已失敗",從而立即觸發fail()
方法。
(7) $.when()
為多個操作指定回撥函式。
除了這些方法以外,deferred物件還有二個重要方法,上面的教程中沒有涉及到。
(8)deferred.then()
有時為了省事,可以把done()
和fail()
合在一起寫,這就是then()
方法。
(9)deferred.progress()
當Deferred(延遲)物件生成時,呼叫已新增的處理程式。
2. jQuery封裝用法
// jQuery Deferred 寫法
var dtd = $.Deferred();// 新建一個Deferred物件
var wait = function(dtd){
var tasks = function(){
alert("執行完畢");
dtd.resolve();//改變Deferred物件的執行狀態 成功狀態
};
setTimeout(tasks,2000);
return dtd;
};
// 延遲物件的狀態 決定呼叫哪個佇列的處理函式
$.when(wait(dtd))
.done(function(){
alert("成功啦");
}).fail(function(){
alert("出錯了");
})
// dtd.resolve(); 改變dtd的執行狀態導致done立刻執行
// dtd.reject(); 改變dtd的執行狀態導致fail立刻執行
// dtd.resolve();
複製程式碼
以上程式碼輸出:
執行完畢
成功啦
通過以上程式碼,我們反推jQuery的原始碼是如何實現的。
3.原始碼的設計思路:
以下僅為核心程式碼片段,完整程式碼請點選原始碼分析下載
(1)首先定義了tuples的資料結構,用來組裝儲存非同步延遲三種不同狀態資訊的描述.
/**
* tuples 定義一個陣列來儲存三種不同狀態資訊的描述
* 第一個引數 延時物件的狀態
* 第二個引數 往佇列裡新增處理函式
* 第三個引數 建立不同狀態的佇列
* 第四個引數 記錄最終狀態資訊
* **/
var tuples = [
["resolve","done",jQuery.Callbacks("once memory"),"resolved"],
["reject","fail",jQuery.Callbacks("once memory"),"rejected"],
["notify","progress",jQuery.Callbacks("memory")]]
複製程式碼
(2)然後定義一個promise
用來封裝state,then,promise
物件
promise = {
state :function(){
return state;
},
then:function() {
},
promise:function(obj) {
console.log(promise);
debugger
return obj !=null ? jQuery.extend(obj,promise):promise;
}
},
複製程式碼
(3)定義一個延遲物件 deferred = {};
(4)遍歷封裝好的tuples陣列佇列,把陣列裡第二個元素也就是對映到Callbacks並且給到list,將陣列裡第三個元素記錄最終狀態的資訊給到stateString,然後把陣列第一個元素即延時物件的狀態對映到Callbacks的add方法上,定義輔助方法deferred[resolveWith]
,deferred[rejectWith]
,deferred[notifyWith]
,最後呼叫Callbacks的fireWith方法實現佇列的回撥。
// 遍歷 tuples
tuples.forEach(function(tuple,i){
var list = tuple[2], // 建立佇列 建立三次 self物件的引用 對映 呼叫Callbacks裡面的方法
stateString = tuple[3]; // 拿到當前最終資訊的描述
// promise[done | fail |progress] 將這三個狀態都拿到Callbacks self裡面方法的引用 新增處理函式
promise[tuple[1]] = list.add;
// Handle state 成功或者失敗
if (stateString) { //新增第一個處理程式
list.add(function(){
// state = [resolved | rejected]
state = stateString;
});
}
// deferred [resolve | reject | notify ] 延時物件的狀態拿到函式的引用
deferred[tuple[0]] = function(){
deferred[tuple[0] + "With"] (this === deferred ? promise : this, arguments);
return this;
};
// list.fireWith 執行佇列並且傳參
// 呼叫佇列中的處理函式 並且給他們傳參 繫結執行時的上下文物件
deferred[tuple[0] + "With"] = list.fireWith;
});
複製程式碼
(5)將deferred返回出去
// Make the deferred a promise
promise.promise(deferred);
return deferred;
複製程式碼
(6)定義一個when方法
// 執行一個或多個物件的延遲函式的回撥函式
when : function(subordinate){
return subordinate.promise();
}
});
複製程式碼
至此,大功告成,實現了jQuery的原始碼剖析,體會了到作者的資料結構佇列處理程式設計思想
Deferred設計圖:
其他
jQuery 原始碼剖析 系列目錄地址:猛戳GitHub
jQuery 原始碼剖析 系列預計寫十篇左右,旨在加深對原生JavaScript 部分知識點的理解和深入,重點講解 jQuery核心功能函式、選擇器、Callback 原理、延時物件原理、事件繫結、jQuery體系結構、委託設計模式、dom操作、動畫佇列等。 如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。如果喜歡或者有所啟發,歡迎 star⭐️,對作者也是一種鼓勵。
關注公眾號回覆:學習 領取前端最新最全學習資料,也可以進群和大佬一起學習交流