DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> JavaScript入門知識 >> jQuery入門知識 >> JQuery特效代碼 >> jQuery源碼分析-05異步隊列 Deferred 使用介紹
jQuery源碼分析-05異步隊列 Deferred 使用介紹
編輯:JQuery特效代碼     
5. 異步隊列 Deferred
5.1 概述
異步隊列是一個鏈式對象,增強對回調函數的管理和調用,用於處理異步任務。
異步隊列有三種狀態:初始化(unresolved),成功(resolved),失敗(rejected)。
執行哪些回調函數依賴於狀態。
狀態變為成功(resolved)或失敗(rejected)後,將保持不變。
回調函數的綁定可以是同步,也可以是異步的,即可以在任何時候綁定。
(本節中的 綁定 注冊 增加 具有相同的含義)
5.2 關鍵方法
先看看jQuery. Deferred()中的關鍵方法
分類
方法
說明
增加
deferred.done()
增加成功回調函數
狀態為成功(resolved)時立即調用
deferred.fail()
增加失敗回調函數
狀態為失敗(rejected)時立即調用
deferred.then()
增加成功回調函數和失敗回調函數到各自的隊列中
便捷方法,兩個參數可以是數組或null
狀態為成功(resolved)時立即調用成功回調函數
狀態為失敗(rejected)時立即調用失敗回調函數
deferred.always()
增加回調函數,同時增加到成功隊列和失敗隊列
狀態已確定(無論成功或失敗)時立即調用回調函數
執行
deferred.resolve()
調用成功回調函數隊列
通過調用deferred.resolveWith()實現
deferred.resolveWith()
使用指定的上下文和參數執行成功回調函數
deferred.reject()
調用失敗回調函數隊列
通過調用deferred.rejectWith()實現
deferred.rejectWith()
使用指定的上下文和參數執行失敗回調函數隊列
其他
deferred.isRejected()
判斷狀態是否為成功(resolved)
deferred.isResolved()
判斷狀態是否為失敗(rejected)
deferred.pipe()
每次調用回調函數之前先調用傳入的成功過濾函數或失敗過濾函數,並將過濾函數的返回值作為回調函數的參數
最終返回一個只讀視圖(調用promise實現)
deferred.promise()
返回deferred的只讀視圖
接下來將會jQuery._Deferred和jQuery.Deferred的源碼詳細剖析。


5.3 jQuery._Deferred
. 代碼如下:
局部變量
// 參考資料:
// 官網文檔 http://api.jquery.com/category/deferred-object/
// Deferred機制 http://www.cnblogs.com/fjzhou/archive/2011/05/30/jquery-source-3.html
// 在jQuery 1.5中使用deferred對象 http://developer.51cto.com/art/201103/248638.htm
// 拿著放大鏡看Promise http://www.cnblogs.com/sanshi/archive/2011/03/11/1981789.html
// Promises/A http://wiki.commonjs.org/wiki/Promises/A
var // Promise methods
// 注意,沒有以下方法:resolveWith resolve rejectWith reject pipe when cancel
// 即不允許調用resolve reject cancel等
promiseMethods = "done fail isResolved isRejected promise then always pipe".split( " " ),
// Static reference to slice
// 靜態引用slice方法,借雞生蛋
sliceDeferred = [].slice;
_Deferred:
_Deferred: function() {
var // callbacks list
// 回調函數數組(這裡不翻譯為隊列,避免概念上的混淆)
callbacks = [],
// stored [ context , args ]
// 存儲上下文、參數,同時還可以標識是否執行完成(fired非空即表示已完成)
// 這裡的“完成”指回調函數數組中“已有”的函數都已執行完成;
// 但是可以再次調用done添加回調函數,添加時fired會被重置為0
fired,
// to avoid firing when already doing so
// 如果已經觸發正在執行,避免再次觸發
firing,
// flag to know if the deferred has been cancelled
// 標識異步隊列是否已被取消,取消後將忽略對done resolve resolveWith的調用
cancelled,
// 異步隊列定義(這才是正主,上邊的局部變量通過閉包引用)
// the deferred itself
deferred = {
// done( f1, f2, ...)
// 增加成功回調函數,狀態為成功(resolved)時立即調用
done: function() {
// 如果已取消,則忽略本次調用
if ( !cancelled ) {
// 將後邊代碼用到的局部變量定義在代碼塊開始處的好處:
// 1.聲明變量,增加代碼可讀性;
// 2.共享變量,提高性能
// 注:多年寫Java的經驗,養成了全局變量在開頭、臨時變量隨用隨定義的習慣,看來JavaScript有些不同
var args = arguments, // 回調函數數組
i, // 遍歷變量
length, // 回調函數數組長度
elem, // 單個回調函數
type, // elem類型
_fired; // 用於臨時備份fired(fired中存儲了上下文和參數)
// 如果已執行完成(即fired中保留了上下文和參數)
// 則備份上下文和參數到_fired,同時將fired置為0
if ( fired ) {
_fired = fired;
fired = 0;
}
// 添加arguments中的函數到回調函數數組
for ( i = 0, length = args.length; i < length; i++ ) {
elem = args[ i ];
type = jQuery.type( elem );
// 如果是數組,則遞歸調用
if ( type === "array" ) {
// 強制指定上下文為deferred,個人認為這裡沒必要指定上下文,因為默認的上下文即為deferred
deferred.done.apply( deferred, elem );
} else if ( type === "function" ) {
callbacks.push( elem );
}
}
// 如果已執行(_fired表示Deferred的狀態是確定的),則立即執行新添加的函數
// 使用之前指定的上下文context和參數args
if ( _fired ) {
deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] );
}
}
return this;
},
// resolve with given context and args
// 執行,使用指定的上下文和參數
resolveWith: function( context, args ) {
// 滿足以下全部條件,才會執行:沒有取消 沒有正在執行 沒有執行完成
// 如果已取消 或 已執行完成 或 正在執行,則忽略本次調用
if ( !cancelled && !fired && !firing ) {
// make sure args are available (#8421)
// 確保args可用,一個避免null、undefined造成ReferenceError的常見技巧
args = args || [];
// 執行過程中將firing改為1
firing = 1;
try {
// 遍歷動態數組的技巧
while( callbacks[ 0 ] ) {
// 注意這裡使用指定的context,而不是this
callbacks.shift().apply( context, args );
}
}
// JavaScript支持try/catch/finally
finally {
fired = [ context, args ];
firing = 0;
}
}
return this;
},
// resolve with this as context and given arguments
// 把狀態設置為Resolved
// 設置的理解不准確,因為是否Resolved,是調用isResolved判斷firing、fired的狀態得到的。
// 可以理解為執行
resolve: function() {
deferred.resolveWith( this, arguments );
return this;
},
// Has this deferred been resolved?
// 是否已執行(或解決)?
// 在執行或已執行完畢,都認為已執行/解決
// “已”可能不准確,因為執行過程中也認為是已執行
isResolved: function() {
// 正在運行中
// 或
// 已運行完(即fired不為空/0)
return !!( firing || fired );
},
// Cancel
// 取消異步隊列
// 設置標記位,清空函數隊列
cancel: function() {
cancelled = 1;
callbacks = [];
return this;
}
};
return deferred;
}

5.4 jQuery.Deferred
. 代碼如下:
// Full fledged deferred (two callbacks list)
// 創建一個完整的異步隊列(包含兩個回調函數數組)
// 異步隊列有三種狀態:初始化(unresolved),成功(resolved),失敗(rejected)。
// 執行哪些回調函數依賴於狀態。
// 狀態變為成功(resolved)或失敗(rejected)後,將保持不變。
Deferred: function( func ) {
// _Deferred本無成功狀態或失敗狀態,有四種狀態:初始化、執行中、執行完畢、已取消
// 為了代碼復用, 內部先實現了一個_Deferred
// failDeferred通過閉包引用
var deferred = jQuery._Deferred(),
failDeferred = jQuery._Deferred(),
promise;
// Add errorDeferred methods, then and promise
jQuery.extend( deferred, {
// 增加成功回調函數和失敗回調函數到各自的隊列中
// 便捷方法,兩個參數可以是數組或null
// 狀態為成功(resolved)時立即調用成功回調函數
// 狀態為失敗(rejected)時立即調用失敗回調函數
then: function( doneCallbacks, failCallbacks ) {
// 上下文在這裡有切換:雖然done返回的是deferred,但是fail指向failDeferred.done,執行fail是上下文變為failDeferred
// 簡單點說就是:
// 調用done時向deferred添加回調函數doneCallbacks
// 調用fail時向failDeferred添加回調函數failCallbacks
// 因此這行表達式執行完後,返回的是failDeferred
deferred.done( doneCallbacks ).fail( failCallbacks );
// 強制返回deferred
return this;
},
// 注冊一個callback函數,無論是resolved或者rejected都會被 調用。
// 其實,是把傳入的函數(數組),同時添加到deferred和failDeferred
// 並沒有像我想象的那樣,存到單獨的函數數組中
always: function() {
// done的上下文設置為deferred,fail的上下文設置為this
// done和fail的上下文不一致嗎?一致!在這裡this等於deferred
// 但是這裡如此設置上下文應該該如何解釋呢?與then的實現有什麼不一樣呢?
// fail指向fail指向failDeferred.done,默認上下文是failDeferred,failDeferred的回調函數數組callbacks是通過閉包引用的,
// 這裡雖然將failDeferred.done方法的上下文設置為deferred,但是不影響failDeferred.done的執行,
// 在failDeferred.done的最後將this替換為deferred,實現鏈式調用,
// 即調用過程中沒有丟失上下文this,可以繼續鏈式調用其他的方法而不會導致this混亂
// 從語法上,always要達到的效果與then要達到的效果一致
// 因此,這行代碼可以改寫為兩行(類似then的實現方式),效果是等價的:
// deferred.done( arguments ).fail( arguments );
// returnr this;
return deferred.done.apply( deferred, arguments ).fail.apply( this, arguments );
},
// 增加失敗回調函數
// 狀態為失敗(rejected)時立即調用
fail: failDeferred.done,
// 使用指定的上下文和參數執行失敗回調函數隊列
// 通過調用failDeferred.rejectWith()實現
rejectWith: failDeferred.resolveWith,
// 調用失敗回調函數隊列
// 通過調用failDeferred.resolve()實現
reject: failDeferred.resolve,
// 判斷狀態是否為成功(resolved)
isRejected: failDeferred.isResolved,
// 每次調用回調函數之前先調用傳入的成功過濾函數或失敗過濾函數,並將過濾函數的返回值作為回調函數的參數
// 最終返回一個只讀視圖(調用promise實現)
// fnDone在狀態是否為成功(resolved)時被調用
// fnFail在狀態是否為失敗(rejected)時被調用
// 關於其他的解釋:
// 1. 有的文章翻譯為“管道機制”,從字面無法理解要表達什麼含義,因此至少是不准確
// 2. 錯誤理解:所謂的pipe,只是把傳入的fnDone和fnFail放到了成功隊列和失敗隊列的數組頭部
pipe: function( fnDone, fnFail ) {
return jQuery.Deferred(function( newDefer ) {
jQuery.each( {
done: [ fnDone, "resolve" ], // done在後文中會指向deferred.done
fail: [ fnFail, "reject" ]
}, function( handler, data ) {
var fn = data[ 0 ],
action = data[ 1 ],
returned;
if ( jQuery.isFunction( fn ) ) {
deferred[ handler ](function() {
returned = fn.apply( this, arguments );
if ( returned && jQuery.isFunction( returned.promise ) ) {
returned.promise().then( newDefer.resolve, newDefer.reject );
} else {
newDefer[ action ]( returned );
}
});
} else {
deferred[ handler ]( newDefer[ action ] );
}
});
}).promise();
},
// Get a promise for this deferred
// If obj is provided, the promise aspect is added to the object
// 返回的是一個不完整的Deferred的接口,沒有resolve和reject,即不能 修改Deferred對象的狀態,
// 這是為了不讓外部函數提早觸發回調函數,可以看作是一種只讀視圖。
//
// 比如$.ajax在1.5版本後不再返回XMLHttpRequest,而是返回一個封裝了 XMLHttpRequest和Deferred對象接口的object。
// 其中Deferred部分就是promise()得到 的,這樣不讓外部函數調用resolve和reject,防止在ajax完成前觸發回調函數。
// 把這兩個函數的調用權限保留給ajax內部。
promise: function( obj ) {
if ( obj == null ) {
// 實際只會執行一次promise,第一次執行的結果被存儲在promise變量中
if ( promise ) {
return promise;
}
promise = obj = {};
}
var i = promiseMethods.length;
// 又一種循環遍歷方式
// 我習慣用:
// for( i = 0; i < len; i++ ) 或 for( i = len-1; i >=0; i-- ) 或 for( i = len; i--; )
// jQuery真是遍地是寶!
while( i-- ) {
obj[ promiseMethods[i] ] = deferred[ promiseMethods[i] ];
}
return obj;
}
});
// Make sure only one callback list will be used
// 成功隊列執行完成後,會執行失敗帶列的取消方法
// 失敗隊列執行完成後,會執行成功隊列的取消方法
// 確保只有一個函數隊列會被執行,即要麼執行成功隊列,要麼執行失敗隊列;
// 即狀態只能是或成功、或失敗,無交叉調用
// deferred和failDeferred的canceled屬性,只能通過閉包引用,因此不用擔心狀態、上下文的混亂
deferred.done( failDeferred.cancel ).fail( deferred.cancel );
// Unexpose cancel
// 隱藏cancel接口,即無法從外部取消成功函數隊列
delete deferred.cancel;
// Call given func if any
// 執行傳入的func函數
if ( func ) {
func.call( deferred, deferred );
}
return deferred;
}

5.5 jQuery.when
. 代碼如下:
// Deferred helper
// 異步隊列工具函數
// firstParam:一個或多個Deferred對象或JavaScript普通對象
when: function( firstParam ) {
var args = arguments,
i = 0,
length = args.length,
count = length,
// 如果arguments.length等於1,並且firstParam是Deferred,則deferred=firstParam
// 否則創建一個新的Deferred對象(如果arguments.length等於0或大於1,則創建一個新的Deferred對象)
// 通過jQuery.isFunction( firstParam.promise )簡單的判斷是否是Deferred對象
deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ?
firstParam :
jQuery.Deferred();
// 構造成功(resolve)回調函數
function resolveFunc( i ) {
return function( value ) {
// 如果傳入的參數大於一個,則將傳入的參數轉換為真正的數組(arguments沒有slice方法,借雞生蛋)
args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
if ( !( --count ) ) {
// Strange bug in FF4:
// Values changed onto the arguments object sometimes end up as undefined values
// outside the $.when method. Cloning the object into a fresh array solves the issue
// 執行成功回調函數隊列,上下文強制為傳入的第一個Deferred對象
deferred.resolveWith( deferred, sliceDeferred.call( args, 0 ) );
}
};
}
// 如果參數多於一個
if ( length > 1 ) {
for( ; i < length; i++ ) {
// 簡單的判斷是否是Deferred對象,是則調用.promise().then(),否則忽略
if ( args[ i ] && jQuery.isFunction( args[ i ].promise ) ) {
// 增加成功回調函數和失敗回調函數到各自的隊列中
args[ i ].promise().then( resolveFunc(i), deferred.reject );
} else {
// 計數器,表示發現不是Deferred對象,而是普通JavaScript對象
--count;
}
}
// 計數器為0時,表示傳入的參數都不是Deferred對象
// 執行成功回調函數隊列,上下文強制為傳入的第一個Deferred對象
if ( !count ) {
deferred.resolveWith( deferred, args );
}
// deferred !== firstParam,即deferred為新創建的Deferred對象
// 即length == 0
} else if ( deferred !== firstParam ) {
// 執行成功回調函數隊列,上下文強制為新創建的Deferred對象
deferred.resolveWith( deferred, length ? [ firstParam ] : [] );
}
// 返回傳入的第一個Deferred或新創建的Deferred對象的只讀視圖
return deferred.promise();
}


5.6 Deferred應用
l jQuery.ajax()
n TODO
5.7 可以學習的技巧
l 閉包
. 代碼如下:
function a(){
var guid = 1;
return function(){
return guid++;
}
}
var defer = a();
console.info( defer() ); // 1
console.info( defer() ); // 2
console.info( defer() ); // 3
console.info( defer() ); // 4

l 避免null、undefined造成ReferenceError的常見技巧
args = args || [];
l 遍歷動態數組的技巧
while( callbacks[ 0 ] ) {
callbacks.shift().apply( context, args );
}
l try/catch/finally 實現錯誤處理
語法
說明
try {
// tryStatements
} catch( exception ) {
// catchStatements
} finally {
// finallyStatements
}
tryStatements
必選項。
可能發生錯誤的語句。
exception
必選項。任何變量名。
exception 的初始化值是扔出的錯誤的值。
catchStatements
可選項。
處理在相關聯的 tryStatement 中發生的錯誤的語句。
finallyStatements
可選項。
在所有其他過程發生之後無條件執行的語句。
l 鏈式對象:通過返回this實現鏈式調用
方法
返回值
done
this(即deferred)
resolveWith
this(即deferred)
resolve
this(即deferred)
cancel
this(即deferred)
l 代碼復用 $.each
jQuery.each( {
done: [ fnDone, "resolve" ], // done在後文中會指向deferred.done
fail: [ fnFail, "reject" ]
}, function( handler, data ) {
// 公共代碼復用
});
5.8 後續
l Deferred在jQuery中的應用
l Deferred的自定義應用
XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved