jQuery原始碼閱讀(十三)---jQuery非同步佇列模組
上一篇部落格分析了Callbacks實現原理,而在jQuery中,Ready函式的實現,非同步佇列以及佇列模組都用到了Callbacks物件。jQuery.ready函式在前面已經做了整理,所以這篇部落格主要是分析Deffered(非同步佇列)和jQuery中非同步佇列的應用。
非同步佇列
延遲物件(非同步佇列)是在回撥物件的基礎上實現的。這個延遲物件維護了三個列表:成功(done)回撥函式列表,失敗(fail)回撥函式列表和進行中(progress)回撥函式列表,之所以有三個回撥函式列表,是因為延遲物件有三種狀態,分別是:resolve(成功), reject(失敗), notify(進行中)。
那麼延遲物件是如何在運用回撥物件模組來實現非同步功能的?
看一個例子:
var dfd = $.Deferred(); //與回撥物件類似,先定義一個延遲物件;
setTimeout( function(){ //當時間滿足一秒時
dfd.resolve(); //相當於$.Callbacks().fire()方法,將加入到done(成功回撥列表)中的函式執行,在這裡將彈出有'Yes'的視窗
}, 1000);
dfd.done(function(){ //相當於$.Callbacks().add(function(){})方法
//不同的是,這裡分別通過done和fail方法,將不同的回撥函式新增到各自的回撥列表中。
alert('Yes');
}).fail(function(){
alert('No'); //如果setTimeOut中,將dfd.resolve()換成dfd.reject(),將彈出有'No'的視窗
})
根據上面分析,我們可以得到這樣一個對應關係:
下來我們看看原始碼中是如何實現的?
原始碼框架
jQuery.extend({
Deferred: function( func ){
//三個回撥函式列表
doneList = $.Callbacks('once memory')
failList = $.Callbacks('once memory')
progressList = $.Callbacks('memory')
//初始狀態
state = 'pending' //表示進行中
//三種狀態和三個回撥函式列表對應
list = {
resolve: doneList,
reject: failList,
notify: progressList
}
promise = {
done: doneList.add,
fail: failList.add,
progress: progressList.add,
state: function(){ },
then: function(){ }, //依次新增doneList,failList和progressList回撥函式
always: function(){ }, //不管觸發resolve函式還是reject函式,都會執行該方法新增的回撥函式
pipe: function(){ },
promise: function(){ } //返回promise物件的一個副本或者擴充套件的延遲物件
}
//重新建一個promise的副本
deferred = promise.promise({});
//給deferred延遲物件新增三種狀態函式
for(key in list)
{
deferred[key] = list[key].fire;
deferred[key + 'With'] = list[key].fireWith;
}
//執行deferred.resolve或者deferred.reject時,要改變初始的狀態,並且一旦狀態確定,便不會再更改,所以有下面的操作
deferred.done(function(){
state = 'resolved'
}, failList.disable, progressList.lock).fail(function(){
state = 'rejected'
}, doneList.disable, progressList.lock);
if( func ) { //Deferred也是支援引數的
func.call(deferred, deferred);
}
return deferred; //返回延遲物件
}
})
在原始碼中,我們可以清楚的看到,延遲物件是如何利用回撥物件來實現回撥函式的新增和執行的。除了這些,可以看到,在Deferred函式中,建立了一個promise物件,另外還又得到了一個deferred物件,這兩個物件之間有什麼區別和聯絡呢?
可以看到,deferred物件由promise物件擴充套件而來,但是又比promise物件多了三個函式,這三個函式都是用於改變延遲物件狀態的。那麼為什麼要用兩個延遲物件? 這是為了保證外部不能對延遲物件的狀態進行改變。
舉個例子:
//函式a返回一個延遲物件
function a(){
var dfd = $.Deferred();
setTimeout( function(){
dfd.resolve();
}, 1000);
return dfd;
}
//將a得到的延遲物件 賦值給一個新的延遲物件
var dfdNew = a().done(function(){
alert("Yes");
}).fail(function(){
alert('No');
});
//造成在函式外部,對延遲物件的狀態進行了改變。
dfdNew.reject();
由於上面的a函式返回的是一個延遲物件deferred,所有包含有resolve,reject和notify方法,因此可以在函式外部去改變延遲物件狀態,而此時我們是不希望外部對延遲物件的狀態進行改變,因此這才利用到promise物件。看下面段程式碼:
function a(){
var dfd = $.Deferred();
setTimeout( function(){
dfd.resolve();
}, 1000);
return dfd.promise();
}
var dfdNew = a().done(function(){
alert("Yes");
}).fail(function(){
alert('No');
});
//此時得到dfdNew延遲物件,並沒有resolve等改變狀態的方法,因此下面的語句會出錯。
dfdNew.reject();
這段程式碼就按照我們的意願來執行了,外部並不能對延遲物件的狀態進行更改,只能通過a函式裡面的dfd.resolve來改變。
非同步佇列的應用
在jQuery中,主要有兩個部分用到了非同步佇列(延遲物件)模組:
$.ajax
和 $().ready()
方法。
在ready函式理解這篇部落格中,整理了jQuery的ready方法,但當時還沒有學習關於回撥物件,延遲物件的模組,所以有一部分沒有深入,下來我們再來縷一縷ready函式。
主要看readyList.add(fn)
這部分程式碼,readyList是一個回撥物件,$.Callbacks('once memory')
,這就相當於Deferred物件中,成功回撥函式列表、失敗回撥函式列表,在jQuery.fn.ready函式中,將fn回撥函式新增進回撥列表中。
在頁面元素載入出來之後,觸發之前註冊的事件處理函式,這個事件處理函式中會去調jQuery.ready()
方法,在這個方法中調readyList.fireWith()
方法,從而實現回撥函式的執行。
ajax
模組暫時還沒有看,所以後期整理時再回過頭來分析對於Deferred延遲物件的應用。
when方法
jQuery擴充套件了一個when
方法,這個方法相當於是對Deferred
物件的一個延伸,相當於對多個延遲物件組合起來進行處理。看下面一個例子:
function a(){
var d = $.Deferred();
d.resolve();
return d;
}
function b(){
var d = $.Deferred();
d.resolve();
return d;
}
$.when(a(), b()).done(function(){
//當a()和b()同時返回一個resolve狀態的延遲物件時,才會觸發doneList回撥列表中的回撥函式
alert("Yes");
}).fail(function(){
//當a()或者b()任意一個返回reject狀態的延遲物件,就會觸發failList列表中的回撥函式
alert("No");
})
//而當引數不是Deferred物件或者無引數時,始終觸發doneList裡面的回撥函式;並且可以通過arguments類陣列訪問到傳的引數。
$.when(123, 8945).done(function(){
alert("成功");
}).fail(function(){
alert("失敗");
})
有上面分析和測試,可以想到,$.when()
和$.when(123, 456)
是相同的處理;只有在引數為延遲物件時,才會判斷這些延遲物件的狀態,從而決定$.when
後面新增的回撥方法執行哪一個。
下來根據原始碼縷一遍:
function( firstParam ) {
//首先將引數轉換成陣列,呼叫[].slice方法
var args = sliceDeferred.call( arguments, 0 ),
i = 0,
length = args.length, //得到引數的個數
pValues = new Array( length ),
count = length,
pCount = length,
//根據引數個數,以及引數型別,設定deferred物件
//當無引數或者多於一個引數時,deferred = $.Deferred()物件;
//當且僅當引數是一個延遲物件型別時,deferred = 這個延遲物件
deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ? firstParam : jQuery.Deferred(),
//deferred物件的一份拷貝,供外界使用,所以是利用promise方法得到,沒有resolve(),reject()以及notify()方法,以為外界不允許對狀態再進行改變
promise = deferred.promise();
//下面這兩個函式先不看
function resolveFunc( i ) {
}
function progressFunc( i ) {
}
//下面利用引數個數分情況處理
if ( length > 1 ) {
//多個引數時,又分當前引數是否為延遲物件
for ( ; i < length; i++ ) {
//是延遲物件,分別向doneList列表,failList列表和progressList列表中新增回撥。
//因為是延遲物件,即有'memory'標誌的回撥物件,如果之前延遲物件的狀態已經確定,此時新增回撥函式之後會立即執行回撥函式。
//並且可以看到,新增到failList中的回撥函式是deferred.reject,也就是說只要引數中有一個延遲物件的狀態是reject,都會觸發最終deferred物件的reject。
//而向doneList和progressList中新增的回撥函式分別是resolveFunc和progressFunc,這兩個函式後面再分析。
if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) {
args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) );
} else {
//如果引數不是延遲物件,count減1.
//這裡說明一下count的含義。when整體的實現思路,是用一個計數器來記錄是否所有的延遲物件都resolve或者都notify了。
//初始值為所有的引數個數,當引數不是延遲物件時,減一,當引數是延遲物件,且一直resolve或者一直notify,也減一。直到減為0時,觸發deferred.resolve或者deferred.notify。
--count;
}
}
if ( !count ) {
//count減為0,觸發deferred.resolve().
deferred.resolveWith( deferred, args );
}
} else if ( deferred !== firstParam ) {
//無引數時,觸發deferred.resolveWith()
//一個引數時,且引數不為延遲物件時,同樣觸發deferred.resolveWith().
deferred.resolveWith( deferred, length ? [ firstParam ] : [] );
}
//一個引數,且引數為延遲物件時,deferred = 這個延遲物件。
//最後返回deferred.promise物件,也就是說,這個延遲物件的狀態是什麼,就去觸發$.when後面新增的回撥函式即可。類似於一個延遲物件的處理。
return promise;
}
下面來說resolveFunc
和 progressFunc
這兩個方法:
//這兩個方法分別是新增進doneList和 progressList回撥列表中的回撥函式
function resolveFunc( i ) {
//返回一個函式,這個函式每一次對計數器count減一,並且判斷在count為0時,直接觸發deferred.resolve。
return function( value ) {
args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
if ( !( --count ) ) {
deferred.resolveWith( deferred, args );
}
};
}
//progressFunc與上面的函式是類似的,不同的是,這是對於progressList新增的回撥函式
//更重要的不同點是: 只要有一個延遲物件的狀態為notify,就都會觸發deferred.notify。
function progressFunc( i ) {
return function( value ) {
pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
deferred.notifyWith( promise, pValues );
};
}
到這裡,關於回撥物件,延遲物件以及延遲物件的擴充套件when都已經分析完了。下來為了加深對於這兩個物件的理解和運用,後面可能會從jQuery
功能模組的ajax
方法來分析,期間對於回撥物件和延遲物件的運用還會重點分析以加深理解。
相關文章
- 原始碼閱讀:SDWebImage(十三)——SDWebImageDownloader原始碼Web
- .Net Core Logging模組原始碼閱讀原始碼
- 原始碼閱讀:AFNetworking(十三)——UIImageView+AFNetworking原始碼UIView
- SOFAJRaft原始碼閱讀-模組啟動過程Raft原始碼
- jQuery原始碼分析jQuery原始碼
- ARTS第十三週(閱讀Tomcat原始碼)Tomcat原始碼
- TiCDC 原始碼閱讀(二)TiKV CDC 模組介紹原始碼
- jQuery 動畫效果 與 動畫佇列jQuery動畫佇列
- jQuery: 動畫佇列與停止動畫 stopjQuery動畫佇列
- 原始碼級深挖AQS佇列同步器原始碼AQS佇列
- 【原始碼閱讀】AndPermission原始碼閱讀原始碼
- jQuery原始碼剖析(四) - Deferred非同步回撥解決方案jQuery原始碼非同步
- jQuery原始碼解析之position()jQuery原始碼
- jQuery原始碼解析之clone()jQuery原始碼
- jQuery2.0.3原始碼分析jQuery原始碼
- jQuery原始碼學習之$()jQuery原始碼
- time模組,collections模組,佇列和棧佇列
- SOFARegistry 原始碼|資料同步模組解析原始碼
- jQuery原始碼解析之replaceWith()/unwrap()jQuery原始碼
- jQuery原始碼學習之eventjQuery原始碼
- jQuery原始碼學習之extendjQuery原始碼
- 【原始碼閱讀】Glide原始碼閱讀之into方法(三)原始碼IDE
- 【原始碼閱讀】Glide原始碼閱讀之with方法(一)原始碼IDE
- 讀Zepto原始碼之Form模組原始碼ORM
- TiFlash 原始碼閱讀(四)TiFlash DDL 模組設計及實現分析原始碼
- 【原始碼閱讀】Glide原始碼閱讀之load方法(二)原始碼IDE
- jQuery原始碼剖析(三) - Callbacks 原理分析jQuery原始碼
- jQuery原始碼剖析 (二) - 選擇器jQuery原始碼
- jQuery原始碼學習筆記一jQuery原始碼筆記
- jQuery1.8.2原始碼學習入手jQuery原始碼
- Vue原始碼閱讀- 批量非同步更新與nextTick原理Vue原始碼非同步
- Vue原始碼閱讀 - 批量非同步更新與nextTick原理Vue原始碼非同步
- Abp 審計模組原始碼解讀原始碼
- Laravel 佇列原始碼解析Laravel佇列原始碼
- AmplifyImpostors原始碼閱讀原始碼
- stack原始碼閱讀原始碼
- AQS原始碼閱讀AQS原始碼
- delta原始碼閱讀原始碼
- CountDownLatch原始碼閱讀CountDownLatch原始碼