jQuery源碼分析-05異步隊列 Deferred 使用介紹
更新時間:2011年11月14日 23:51:41 作者:
異步隊列是一個鏈式對象,增強對回調(diào)函數(shù)的管理和調(diào)用,用于處理異步任務(wù)
5. 異步隊列 Deferred
5.1 概述
異步隊列是一個鏈式對象,增強對回調(diào)函數(shù)的管理和調(diào)用,用于處理異步任務(wù)。
異步隊列有三種狀態(tài):初始化(unresolved),成功(resolved),失?。╮ejected)。
執(zhí)行哪些回調(diào)函數(shù)依賴于狀態(tài)。
狀態(tài)變?yōu)槌晒Γ╮esolved)或失?。╮ejected)后,將保持不變。
回調(diào)函數(shù)的綁定可以是同步,也可以是異步的,即可以在任何時候綁定。
(本節(jié)中的 綁定 注冊 增加 具有相同的含義)
5.2 關(guān)鍵方法
先看看jQuery. Deferred()中的關(guān)鍵方法
分類
方法
說明
增加
deferred.done()
增加成功回調(diào)函數(shù)
狀態(tài)為成功(resolved)時立即調(diào)用
deferred.fail()
增加失敗回調(diào)函數(shù)
狀態(tài)為失敗(rejected)時立即調(diào)用
deferred.then()
增加成功回調(diào)函數(shù)和失敗回調(diào)函數(shù)到各自的隊列中
便捷方法,兩個參數(shù)可以是數(shù)組或null
狀態(tài)為成功(resolved)時立即調(diào)用成功回調(diào)函數(shù)
狀態(tài)為失敗(rejected)時立即調(diào)用失敗回調(diào)函數(shù)
deferred.always()
增加回調(diào)函數(shù),同時增加到成功隊列和失敗隊列
狀態(tài)已確定(無論成功或失?。r立即調(diào)用回調(diào)函數(shù)
執(zhí)行
deferred.resolve()
調(diào)用成功回調(diào)函數(shù)隊列
通過調(diào)用deferred.resolveWith()實現(xiàn)
deferred.resolveWith()
使用指定的上下文和參數(shù)執(zhí)行成功回調(diào)函數(shù)
deferred.reject()
調(diào)用失敗回調(diào)函數(shù)隊列
通過調(diào)用deferred.rejectWith()實現(xiàn)
deferred.rejectWith()
使用指定的上下文和參數(shù)執(zhí)行失敗回調(diào)函數(shù)隊列
其他
deferred.isRejected()
判斷狀態(tài)是否為成功(resolved)
deferred.isResolved()
判斷狀態(tài)是否為失敗(rejected)
deferred.pipe()
每次調(diào)用回調(diào)函數(shù)之前先調(diào)用傳入的成功過濾函數(shù)或失敗過濾函數(shù),并將過濾函數(shù)的返回值作為回調(diào)函數(shù)的參數(shù)
最終返回一個只讀視圖(調(diào)用promise實現(xiàn))
deferred.promise()
返回deferred的只讀視圖
接下來將會jQuery._Deferred和jQuery.Deferred的源碼詳細剖析。
5.3 jQuery._Deferred
局部變量
// 參考資料:
// 官網(wǎng)文檔 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
// 即不允許調(diào)用resolve reject cancel等
promiseMethods = "done fail isResolved isRejected promise then always pipe".split( " " ),
// Static reference to slice
// 靜態(tài)引用slice方法,借雞生蛋
sliceDeferred = [].slice;
_Deferred:
_Deferred: function() {
var // callbacks list
// 回調(diào)函數(shù)數(shù)組(這里不翻譯為隊列,避免概念上的混淆)
callbacks = [],
// stored [ context , args ]
// 存儲上下文、參數(shù),同時還可以標識是否執(zhí)行完成(fired非空即表示已完成)
// 這里的“完成”指回調(diào)函數(shù)數(shù)組中“已有”的函數(shù)都已執(zhí)行完成;
// 但是可以再次調(diào)用done添加回調(diào)函數(shù),添加時fired會被重置為0
fired,
// to avoid firing when already doing so
// 如果已經(jīng)觸發(fā)正在執(zhí)行,避免再次觸發(fā)
firing,
// flag to know if the deferred has been cancelled
// 標識異步隊列是否已被取消,取消后將忽略對done resolve resolveWith的調(diào)用
cancelled,
// 異步隊列定義(這才是正主,上邊的局部變量通過閉包引用)
// the deferred itself
deferred = {
// done( f1, f2, ...)
// 增加成功回調(diào)函數(shù),狀態(tài)為成功(resolved)時立即調(diào)用
done: function() {
// 如果已取消,則忽略本次調(diào)用
if ( !cancelled ) {
// 將后邊代碼用到的局部變量定義在代碼塊開始處的好處:
// 1.聲明變量,增加代碼可讀性;
// 2.共享變量,提高性能
// 注:多年寫Java的經(jīng)驗,養(yǎng)成了全局變量在開頭、臨時變量隨用隨定義的習(xí)慣,看來JavaScript有些不同
var args = arguments, // 回調(diào)函數(shù)數(shù)組
i, // 遍歷變量
length, // 回調(diào)函數(shù)數(shù)組長度
elem, // 單個回調(diào)函數(shù)
type, // elem類型
_fired; // 用于臨時備份fired(fired中存儲了上下文和參數(shù))
// 如果已執(zhí)行完成(即fired中保留了上下文和參數(shù))
// 則備份上下文和參數(shù)到_fired,同時將fired置為0
if ( fired ) {
_fired = fired;
fired = 0;
}
// 添加arguments中的函數(shù)到回調(diào)函數(shù)數(shù)組
for ( i = 0, length = args.length; i < length; i++ ) {
elem = args[ i ];
type = jQuery.type( elem );
// 如果是數(shù)組,則遞歸調(diào)用
if ( type === "array" ) {
// 強制指定上下文為deferred,個人認為這里沒必要指定上下文,因為默認的上下文即為deferred
deferred.done.apply( deferred, elem );
} else if ( type === "function" ) {
callbacks.push( elem );
}
}
// 如果已執(zhí)行(_fired表示Deferred的狀態(tài)是確定的),則立即執(zhí)行新添加的函數(shù)
// 使用之前指定的上下文context和參數(shù)args
if ( _fired ) {
deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] );
}
}
return this;
},
// resolve with given context and args
// 執(zhí)行,使用指定的上下文和參數(shù)
resolveWith: function( context, args ) {
// 滿足以下全部條件,才會執(zhí)行:沒有取消 沒有正在執(zhí)行 沒有執(zhí)行完成
// 如果已取消 或 已執(zhí)行完成 或 正在執(zhí)行,則忽略本次調(diào)用
if ( !cancelled && !fired && !firing ) {
// make sure args are available (#8421)
// 確保args可用,一個避免null、undefined造成ReferenceError的常見技巧
args = args || [];
// 執(zhí)行過程中將firing改為1
firing = 1;
try {
// 遍歷動態(tài)數(shù)組的技巧
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
// 把狀態(tài)設(shè)置為Resolved
// 設(shè)置的理解不準確,因為是否Resolved,是調(diào)用isResolved判斷firing、fired的狀態(tài)得到的。
// 可以理解為執(zhí)行
resolve: function() {
deferred.resolveWith( this, arguments );
return this;
},
// Has this deferred been resolved?
// 是否已執(zhí)行(或解決)?
// 在執(zhí)行或已執(zhí)行完畢,都認為已執(zhí)行/解決
// “已”可能不準確,因為執(zhí)行過程中也認為是已執(zhí)行
isResolved: function() {
// 正在運行中
// 或
// 已運行完(即fired不為空/0)
return !!( firing || fired );
},
// Cancel
// 取消異步隊列
// 設(shè)置標記位,清空函數(shù)隊列
cancel: function() {
cancelled = 1;
callbacks = [];
return this;
}
};
return deferred;
}
5.4 jQuery.Deferred
// Full fledged deferred (two callbacks list)
// 創(chuàng)建一個完整的異步隊列(包含兩個回調(diào)函數(shù)數(shù)組)
// 異步隊列有三種狀態(tài):初始化(unresolved),成功(resolved),失?。╮ejected)。
// 執(zhí)行哪些回調(diào)函數(shù)依賴于狀態(tài)。
// 狀態(tài)變?yōu)槌晒Γ╮esolved)或失?。╮ejected)后,將保持不變。
Deferred: function( func ) {
// _Deferred本無成功狀態(tài)或失敗狀態(tài),有四種狀態(tài):初始化、執(zhí)行中、執(zhí)行完畢、已取消
// 為了代碼復(fù)用, 內(nèi)部先實現(xiàn)了一個_Deferred
// failDeferred通過閉包引用
var deferred = jQuery._Deferred(),
failDeferred = jQuery._Deferred(),
promise;
// Add errorDeferred methods, then and promise
jQuery.extend( deferred, {
// 增加成功回調(diào)函數(shù)和失敗回調(diào)函數(shù)到各自的隊列中
// 便捷方法,兩個參數(shù)可以是數(shù)組或null
// 狀態(tài)為成功(resolved)時立即調(diào)用成功回調(diào)函數(shù)
// 狀態(tài)為失?。╮ejected)時立即調(diào)用失敗回調(diào)函數(shù)
then: function( doneCallbacks, failCallbacks ) {
// 上下文在這里有切換:雖然done返回的是deferred,但是fail指向failDeferred.done,執(zhí)行fail是上下文變?yōu)閒ailDeferred
// 簡單點說就是:
// 調(diào)用done時向deferred添加回調(diào)函數(shù)doneCallbacks
// 調(diào)用fail時向failDeferred添加回調(diào)函數(shù)failCallbacks
// 因此這行表達式執(zhí)行完后,返回的是failDeferred
deferred.done( doneCallbacks ).fail( failCallbacks );
// 強制返回deferred
return this;
},
// 注冊一個callback函數(shù),無論是resolved或者rejected都會被 調(diào)用。
// 其實,是把傳入的函數(shù)(數(shù)組),同時添加到deferred和failDeferred
// 并沒有像我想象的那樣,存到單獨的函數(shù)數(shù)組中
always: function() {
// done的上下文設(shè)置為deferred,fail的上下文設(shè)置為this
// done和fail的上下文不一致嗎?一致!在這里this等于deferred
// 但是這里如此設(shè)置上下文應(yīng)該該如何解釋呢?與then的實現(xiàn)有什么不一樣呢?
// fail指向fail指向failDeferred.done,默認上下文是failDeferred,failDeferred的回調(diào)函數(shù)數(shù)組callbacks是通過閉包引用的,
// 這里雖然將failDeferred.done方法的上下文設(shè)置為deferred,但是不影響failDeferred.done的執(zhí)行,
// 在failDeferred.done的最后將this替換為deferred,實現(xiàn)鏈式調(diào)用,
// 即調(diào)用過程中沒有丟失上下文this,可以繼續(xù)鏈式調(diào)用其他的方法而不會導(dǎo)致this混亂
// 從語法上,always要達到的效果與then要達到的效果一致
// 因此,這行代碼可以改寫為兩行(類似then的實現(xiàn)方式),效果是等價的:
// deferred.done( arguments ).fail( arguments );
// returnr this;
return deferred.done.apply( deferred, arguments ).fail.apply( this, arguments );
},
// 增加失敗回調(diào)函數(shù)
// 狀態(tài)為失?。╮ejected)時立即調(diào)用
fail: failDeferred.done,
// 使用指定的上下文和參數(shù)執(zhí)行失敗回調(diào)函數(shù)隊列
// 通過調(diào)用failDeferred.rejectWith()實現(xiàn)
rejectWith: failDeferred.resolveWith,
// 調(diào)用失敗回調(diào)函數(shù)隊列
// 通過調(diào)用failDeferred.resolve()實現(xiàn)
reject: failDeferred.resolve,
// 判斷狀態(tài)是否為成功(resolved)
isRejected: failDeferred.isResolved,
// 每次調(diào)用回調(diào)函數(shù)之前先調(diào)用傳入的成功過濾函數(shù)或失敗過濾函數(shù),并將過濾函數(shù)的返回值作為回調(diào)函數(shù)的參數(shù)
// 最終返回一個只讀視圖(調(diào)用promise實現(xiàn))
// fnDone在狀態(tài)是否為成功(resolved)時被調(diào)用
// fnFail在狀態(tài)是否為失?。╮ejected)時被調(diào)用
// 關(guān)于其他的解釋:
// 1. 有的文章翻譯為“管道機制”,從字面無法理解要表達什么含義,因此至少是不準確
// 2. 錯誤理解:所謂的pipe,只是把傳入的fnDone和fnFail放到了成功隊列和失敗隊列的數(shù)組頭部
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對象的狀態(tài),
// 這是為了不讓外部函數(shù)提早觸發(fā)回調(diào)函數(shù),可以看作是一種只讀視圖。
//
// 比如$.ajax在1.5版本后不再返回XMLHttpRequest,而是返回一個封裝了 XMLHttpRequest和Deferred對象接口的object。
// 其中Deferred部分就是promise()得到 的,這樣不讓外部函數(shù)調(diào)用resolve和reject,防止在ajax完成前觸發(fā)回調(diào)函數(shù)。
// 把這兩個函數(shù)的調(diào)用權(quán)限保留給ajax內(nèi)部。
promise: function( obj ) {
if ( obj == null ) {
// 實際只會執(zhí)行一次promise,第一次執(zhí)行的結(jié)果被存儲在promise變量中
if ( promise ) {
return promise;
}
promise = obj = {};
}
var i = promiseMethods.length;
// 又一種循環(huán)遍歷方式
// 我習(xí)慣用:
// 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
// 成功隊列執(zhí)行完成后,會執(zhí)行失敗帶列的取消方法
// 失敗隊列執(zhí)行完成后,會執(zhí)行成功隊列的取消方法
// 確保只有一個函數(shù)隊列會被執(zhí)行,即要么執(zhí)行成功隊列,要么執(zhí)行失敗隊列;
// 即狀態(tài)只能是或成功、或失敗,無交叉調(diào)用
// deferred和failDeferred的canceled屬性,只能通過閉包引用,因此不用擔(dān)心狀態(tài)、上下文的混亂
deferred.done( failDeferred.cancel ).fail( deferred.cancel );
// Unexpose cancel
// 隱藏cancel接口,即無法從外部取消成功函數(shù)隊列
delete deferred.cancel;
// Call given func if any
// 執(zhí)行傳入的func函數(shù)
if ( func ) {
func.call( deferred, deferred );
}
return deferred;
}
5.5 jQuery.when
// Deferred helper
// 異步隊列工具函數(shù)
// firstParam:一個或多個Deferred對象或JavaScript普通對象
when: function( firstParam ) {
var args = arguments,
i = 0,
length = args.length,
count = length,
// 如果arguments.length等于1,并且firstParam是Deferred,則deferred=firstParam
// 否則創(chuàng)建一個新的Deferred對象(如果arguments.length等于0或大于1,則創(chuàng)建一個新的Deferred對象)
// 通過jQuery.isFunction( firstParam.promise )簡單的判斷是否是Deferred對象
deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ?
firstParam :
jQuery.Deferred();
// 構(gòu)造成功(resolve)回調(diào)函數(shù)
function resolveFunc( i ) {
return function( value ) {
// 如果傳入的參數(shù)大于一個,則將傳入的參數(shù)轉(zhuǎn)換為真正的數(shù)組(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
// 執(zhí)行成功回調(diào)函數(shù)隊列,上下文強制為傳入的第一個Deferred對象
deferred.resolveWith( deferred, sliceDeferred.call( args, 0 ) );
}
};
}
// 如果參數(shù)多于一個
if ( length > 1 ) {
for( ; i < length; i++ ) {
// 簡單的判斷是否是Deferred對象,是則調(diào)用.promise().then(),否則忽略
if ( args[ i ] && jQuery.isFunction( args[ i ].promise ) ) {
// 增加成功回調(diào)函數(shù)和失敗回調(diào)函數(shù)到各自的隊列中
args[ i ].promise().then( resolveFunc(i), deferred.reject );
} else {
// 計數(shù)器,表示發(fā)現(xiàn)不是Deferred對象,而是普通JavaScript對象
--count;
}
}
// 計數(shù)器為0時,表示傳入的參數(shù)都不是Deferred對象
// 執(zhí)行成功回調(diào)函數(shù)隊列,上下文強制為傳入的第一個Deferred對象
if ( !count ) {
deferred.resolveWith( deferred, args );
}
// deferred !== firstParam,即deferred為新創(chuàng)建的Deferred對象
// 即length == 0
} else if ( deferred !== firstParam ) {
// 執(zhí)行成功回調(diào)函數(shù)隊列,上下文強制為新創(chuàng)建的Deferred對象
deferred.resolveWith( deferred, length ? [ firstParam ] : [] );
}
// 返回傳入的第一個Deferred或新創(chuàng)建的Deferred對象的只讀視圖
return deferred.promise();
}
5.6 Deferred應(yīng)用
l jQuery.ajax()
n TODO
5.7 可以學(xué)習(xí)的技巧
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 遍歷動態(tài)數(shù)組的技巧
while( callbacks[ 0 ] ) {
callbacks.shift().apply( context, args );
}
l try/catch/finally 實現(xiàn)錯誤處理
語法
說明
try {
// tryStatements
} catch( exception ) {
// catchStatements
} finally {
// finallyStatements
}
tryStatements
必選項。
可能發(fā)生錯誤的語句。
exception
必選項。任何變量名。
exception 的初始化值是扔出的錯誤的值。
catchStatements
可選項。
處理在相關(guān)聯(lián)的 tryStatement 中發(fā)生的錯誤的語句。
finallyStatements
可選項。
在所有其他過程發(fā)生之后無條件執(zhí)行的語句。
l 鏈式對象:通過返回this實現(xiàn)鏈式調(diào)用
方法
返回值
done
this(即deferred)
resolveWith
this(即deferred)
resolve
this(即deferred)
cancel
this(即deferred)
l 代碼復(fù)用 $.each
jQuery.each( {
done: [ fnDone, "resolve" ], // done在后文中會指向deferred.done
fail: [ fnFail, "reject" ]
}, function( handler, data ) {
// 公共代碼復(fù)用
});
5.8 后續(xù)
l Deferred在jQuery中的應(yīng)用
l Deferred的自定義應(yīng)用
5.1 概述
異步隊列是一個鏈式對象,增強對回調(diào)函數(shù)的管理和調(diào)用,用于處理異步任務(wù)。
異步隊列有三種狀態(tài):初始化(unresolved),成功(resolved),失?。╮ejected)。
執(zhí)行哪些回調(diào)函數(shù)依賴于狀態(tài)。
狀態(tài)變?yōu)槌晒Γ╮esolved)或失?。╮ejected)后,將保持不變。
回調(diào)函數(shù)的綁定可以是同步,也可以是異步的,即可以在任何時候綁定。
(本節(jié)中的 綁定 注冊 增加 具有相同的含義)
5.2 關(guān)鍵方法
先看看jQuery. Deferred()中的關(guān)鍵方法
分類
方法
說明
增加
deferred.done()
增加成功回調(diào)函數(shù)
狀態(tài)為成功(resolved)時立即調(diào)用
deferred.fail()
增加失敗回調(diào)函數(shù)
狀態(tài)為失敗(rejected)時立即調(diào)用
deferred.then()
增加成功回調(diào)函數(shù)和失敗回調(diào)函數(shù)到各自的隊列中
便捷方法,兩個參數(shù)可以是數(shù)組或null
狀態(tài)為成功(resolved)時立即調(diào)用成功回調(diào)函數(shù)
狀態(tài)為失敗(rejected)時立即調(diào)用失敗回調(diào)函數(shù)
deferred.always()
增加回調(diào)函數(shù),同時增加到成功隊列和失敗隊列
狀態(tài)已確定(無論成功或失?。r立即調(diào)用回調(diào)函數(shù)
執(zhí)行
deferred.resolve()
調(diào)用成功回調(diào)函數(shù)隊列
通過調(diào)用deferred.resolveWith()實現(xiàn)
deferred.resolveWith()
使用指定的上下文和參數(shù)執(zhí)行成功回調(diào)函數(shù)
deferred.reject()
調(diào)用失敗回調(diào)函數(shù)隊列
通過調(diào)用deferred.rejectWith()實現(xiàn)
deferred.rejectWith()
使用指定的上下文和參數(shù)執(zhí)行失敗回調(diào)函數(shù)隊列
其他
deferred.isRejected()
判斷狀態(tài)是否為成功(resolved)
deferred.isResolved()
判斷狀態(tài)是否為失敗(rejected)
deferred.pipe()
每次調(diào)用回調(diào)函數(shù)之前先調(diào)用傳入的成功過濾函數(shù)或失敗過濾函數(shù),并將過濾函數(shù)的返回值作為回調(diào)函數(shù)的參數(shù)
最終返回一個只讀視圖(調(diào)用promise實現(xiàn))
deferred.promise()
返回deferred的只讀視圖
接下來將會jQuery._Deferred和jQuery.Deferred的源碼詳細剖析。
5.3 jQuery._Deferred
復(fù)制代碼 代碼如下:
局部變量
// 參考資料:
// 官網(wǎng)文檔 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
// 即不允許調(diào)用resolve reject cancel等
promiseMethods = "done fail isResolved isRejected promise then always pipe".split( " " ),
// Static reference to slice
// 靜態(tài)引用slice方法,借雞生蛋
sliceDeferred = [].slice;
_Deferred:
_Deferred: function() {
var // callbacks list
// 回調(diào)函數(shù)數(shù)組(這里不翻譯為隊列,避免概念上的混淆)
callbacks = [],
// stored [ context , args ]
// 存儲上下文、參數(shù),同時還可以標識是否執(zhí)行完成(fired非空即表示已完成)
// 這里的“完成”指回調(diào)函數(shù)數(shù)組中“已有”的函數(shù)都已執(zhí)行完成;
// 但是可以再次調(diào)用done添加回調(diào)函數(shù),添加時fired會被重置為0
fired,
// to avoid firing when already doing so
// 如果已經(jīng)觸發(fā)正在執(zhí)行,避免再次觸發(fā)
firing,
// flag to know if the deferred has been cancelled
// 標識異步隊列是否已被取消,取消后將忽略對done resolve resolveWith的調(diào)用
cancelled,
// 異步隊列定義(這才是正主,上邊的局部變量通過閉包引用)
// the deferred itself
deferred = {
// done( f1, f2, ...)
// 增加成功回調(diào)函數(shù),狀態(tài)為成功(resolved)時立即調(diào)用
done: function() {
// 如果已取消,則忽略本次調(diào)用
if ( !cancelled ) {
// 將后邊代碼用到的局部變量定義在代碼塊開始處的好處:
// 1.聲明變量,增加代碼可讀性;
// 2.共享變量,提高性能
// 注:多年寫Java的經(jīng)驗,養(yǎng)成了全局變量在開頭、臨時變量隨用隨定義的習(xí)慣,看來JavaScript有些不同
var args = arguments, // 回調(diào)函數(shù)數(shù)組
i, // 遍歷變量
length, // 回調(diào)函數(shù)數(shù)組長度
elem, // 單個回調(diào)函數(shù)
type, // elem類型
_fired; // 用于臨時備份fired(fired中存儲了上下文和參數(shù))
// 如果已執(zhí)行完成(即fired中保留了上下文和參數(shù))
// 則備份上下文和參數(shù)到_fired,同時將fired置為0
if ( fired ) {
_fired = fired;
fired = 0;
}
// 添加arguments中的函數(shù)到回調(diào)函數(shù)數(shù)組
for ( i = 0, length = args.length; i < length; i++ ) {
elem = args[ i ];
type = jQuery.type( elem );
// 如果是數(shù)組,則遞歸調(diào)用
if ( type === "array" ) {
// 強制指定上下文為deferred,個人認為這里沒必要指定上下文,因為默認的上下文即為deferred
deferred.done.apply( deferred, elem );
} else if ( type === "function" ) {
callbacks.push( elem );
}
}
// 如果已執(zhí)行(_fired表示Deferred的狀態(tài)是確定的),則立即執(zhí)行新添加的函數(shù)
// 使用之前指定的上下文context和參數(shù)args
if ( _fired ) {
deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] );
}
}
return this;
},
// resolve with given context and args
// 執(zhí)行,使用指定的上下文和參數(shù)
resolveWith: function( context, args ) {
// 滿足以下全部條件,才會執(zhí)行:沒有取消 沒有正在執(zhí)行 沒有執(zhí)行完成
// 如果已取消 或 已執(zhí)行完成 或 正在執(zhí)行,則忽略本次調(diào)用
if ( !cancelled && !fired && !firing ) {
// make sure args are available (#8421)
// 確保args可用,一個避免null、undefined造成ReferenceError的常見技巧
args = args || [];
// 執(zhí)行過程中將firing改為1
firing = 1;
try {
// 遍歷動態(tài)數(shù)組的技巧
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
// 把狀態(tài)設(shè)置為Resolved
// 設(shè)置的理解不準確,因為是否Resolved,是調(diào)用isResolved判斷firing、fired的狀態(tài)得到的。
// 可以理解為執(zhí)行
resolve: function() {
deferred.resolveWith( this, arguments );
return this;
},
// Has this deferred been resolved?
// 是否已執(zhí)行(或解決)?
// 在執(zhí)行或已執(zhí)行完畢,都認為已執(zhí)行/解決
// “已”可能不準確,因為執(zhí)行過程中也認為是已執(zhí)行
isResolved: function() {
// 正在運行中
// 或
// 已運行完(即fired不為空/0)
return !!( firing || fired );
},
// Cancel
// 取消異步隊列
// 設(shè)置標記位,清空函數(shù)隊列
cancel: function() {
cancelled = 1;
callbacks = [];
return this;
}
};
return deferred;
}
5.4 jQuery.Deferred
復(fù)制代碼 代碼如下:
// Full fledged deferred (two callbacks list)
// 創(chuàng)建一個完整的異步隊列(包含兩個回調(diào)函數(shù)數(shù)組)
// 異步隊列有三種狀態(tài):初始化(unresolved),成功(resolved),失?。╮ejected)。
// 執(zhí)行哪些回調(diào)函數(shù)依賴于狀態(tài)。
// 狀態(tài)變?yōu)槌晒Γ╮esolved)或失?。╮ejected)后,將保持不變。
Deferred: function( func ) {
// _Deferred本無成功狀態(tài)或失敗狀態(tài),有四種狀態(tài):初始化、執(zhí)行中、執(zhí)行完畢、已取消
// 為了代碼復(fù)用, 內(nèi)部先實現(xiàn)了一個_Deferred
// failDeferred通過閉包引用
var deferred = jQuery._Deferred(),
failDeferred = jQuery._Deferred(),
promise;
// Add errorDeferred methods, then and promise
jQuery.extend( deferred, {
// 增加成功回調(diào)函數(shù)和失敗回調(diào)函數(shù)到各自的隊列中
// 便捷方法,兩個參數(shù)可以是數(shù)組或null
// 狀態(tài)為成功(resolved)時立即調(diào)用成功回調(diào)函數(shù)
// 狀態(tài)為失?。╮ejected)時立即調(diào)用失敗回調(diào)函數(shù)
then: function( doneCallbacks, failCallbacks ) {
// 上下文在這里有切換:雖然done返回的是deferred,但是fail指向failDeferred.done,執(zhí)行fail是上下文變?yōu)閒ailDeferred
// 簡單點說就是:
// 調(diào)用done時向deferred添加回調(diào)函數(shù)doneCallbacks
// 調(diào)用fail時向failDeferred添加回調(diào)函數(shù)failCallbacks
// 因此這行表達式執(zhí)行完后,返回的是failDeferred
deferred.done( doneCallbacks ).fail( failCallbacks );
// 強制返回deferred
return this;
},
// 注冊一個callback函數(shù),無論是resolved或者rejected都會被 調(diào)用。
// 其實,是把傳入的函數(shù)(數(shù)組),同時添加到deferred和failDeferred
// 并沒有像我想象的那樣,存到單獨的函數(shù)數(shù)組中
always: function() {
// done的上下文設(shè)置為deferred,fail的上下文設(shè)置為this
// done和fail的上下文不一致嗎?一致!在這里this等于deferred
// 但是這里如此設(shè)置上下文應(yīng)該該如何解釋呢?與then的實現(xiàn)有什么不一樣呢?
// fail指向fail指向failDeferred.done,默認上下文是failDeferred,failDeferred的回調(diào)函數(shù)數(shù)組callbacks是通過閉包引用的,
// 這里雖然將failDeferred.done方法的上下文設(shè)置為deferred,但是不影響failDeferred.done的執(zhí)行,
// 在failDeferred.done的最后將this替換為deferred,實現(xiàn)鏈式調(diào)用,
// 即調(diào)用過程中沒有丟失上下文this,可以繼續(xù)鏈式調(diào)用其他的方法而不會導(dǎo)致this混亂
// 從語法上,always要達到的效果與then要達到的效果一致
// 因此,這行代碼可以改寫為兩行(類似then的實現(xiàn)方式),效果是等價的:
// deferred.done( arguments ).fail( arguments );
// returnr this;
return deferred.done.apply( deferred, arguments ).fail.apply( this, arguments );
},
// 增加失敗回調(diào)函數(shù)
// 狀態(tài)為失?。╮ejected)時立即調(diào)用
fail: failDeferred.done,
// 使用指定的上下文和參數(shù)執(zhí)行失敗回調(diào)函數(shù)隊列
// 通過調(diào)用failDeferred.rejectWith()實現(xiàn)
rejectWith: failDeferred.resolveWith,
// 調(diào)用失敗回調(diào)函數(shù)隊列
// 通過調(diào)用failDeferred.resolve()實現(xiàn)
reject: failDeferred.resolve,
// 判斷狀態(tài)是否為成功(resolved)
isRejected: failDeferred.isResolved,
// 每次調(diào)用回調(diào)函數(shù)之前先調(diào)用傳入的成功過濾函數(shù)或失敗過濾函數(shù),并將過濾函數(shù)的返回值作為回調(diào)函數(shù)的參數(shù)
// 最終返回一個只讀視圖(調(diào)用promise實現(xiàn))
// fnDone在狀態(tài)是否為成功(resolved)時被調(diào)用
// fnFail在狀態(tài)是否為失?。╮ejected)時被調(diào)用
// 關(guān)于其他的解釋:
// 1. 有的文章翻譯為“管道機制”,從字面無法理解要表達什么含義,因此至少是不準確
// 2. 錯誤理解:所謂的pipe,只是把傳入的fnDone和fnFail放到了成功隊列和失敗隊列的數(shù)組頭部
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對象的狀態(tài),
// 這是為了不讓外部函數(shù)提早觸發(fā)回調(diào)函數(shù),可以看作是一種只讀視圖。
//
// 比如$.ajax在1.5版本后不再返回XMLHttpRequest,而是返回一個封裝了 XMLHttpRequest和Deferred對象接口的object。
// 其中Deferred部分就是promise()得到 的,這樣不讓外部函數(shù)調(diào)用resolve和reject,防止在ajax完成前觸發(fā)回調(diào)函數(shù)。
// 把這兩個函數(shù)的調(diào)用權(quán)限保留給ajax內(nèi)部。
promise: function( obj ) {
if ( obj == null ) {
// 實際只會執(zhí)行一次promise,第一次執(zhí)行的結(jié)果被存儲在promise變量中
if ( promise ) {
return promise;
}
promise = obj = {};
}
var i = promiseMethods.length;
// 又一種循環(huán)遍歷方式
// 我習(xí)慣用:
// 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
// 成功隊列執(zhí)行完成后,會執(zhí)行失敗帶列的取消方法
// 失敗隊列執(zhí)行完成后,會執(zhí)行成功隊列的取消方法
// 確保只有一個函數(shù)隊列會被執(zhí)行,即要么執(zhí)行成功隊列,要么執(zhí)行失敗隊列;
// 即狀態(tài)只能是或成功、或失敗,無交叉調(diào)用
// deferred和failDeferred的canceled屬性,只能通過閉包引用,因此不用擔(dān)心狀態(tài)、上下文的混亂
deferred.done( failDeferred.cancel ).fail( deferred.cancel );
// Unexpose cancel
// 隱藏cancel接口,即無法從外部取消成功函數(shù)隊列
delete deferred.cancel;
// Call given func if any
// 執(zhí)行傳入的func函數(shù)
if ( func ) {
func.call( deferred, deferred );
}
return deferred;
}
5.5 jQuery.when
復(fù)制代碼 代碼如下:
// Deferred helper
// 異步隊列工具函數(shù)
// firstParam:一個或多個Deferred對象或JavaScript普通對象
when: function( firstParam ) {
var args = arguments,
i = 0,
length = args.length,
count = length,
// 如果arguments.length等于1,并且firstParam是Deferred,則deferred=firstParam
// 否則創(chuàng)建一個新的Deferred對象(如果arguments.length等于0或大于1,則創(chuàng)建一個新的Deferred對象)
// 通過jQuery.isFunction( firstParam.promise )簡單的判斷是否是Deferred對象
deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ?
firstParam :
jQuery.Deferred();
// 構(gòu)造成功(resolve)回調(diào)函數(shù)
function resolveFunc( i ) {
return function( value ) {
// 如果傳入的參數(shù)大于一個,則將傳入的參數(shù)轉(zhuǎn)換為真正的數(shù)組(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
// 執(zhí)行成功回調(diào)函數(shù)隊列,上下文強制為傳入的第一個Deferred對象
deferred.resolveWith( deferred, sliceDeferred.call( args, 0 ) );
}
};
}
// 如果參數(shù)多于一個
if ( length > 1 ) {
for( ; i < length; i++ ) {
// 簡單的判斷是否是Deferred對象,是則調(diào)用.promise().then(),否則忽略
if ( args[ i ] && jQuery.isFunction( args[ i ].promise ) ) {
// 增加成功回調(diào)函數(shù)和失敗回調(diào)函數(shù)到各自的隊列中
args[ i ].promise().then( resolveFunc(i), deferred.reject );
} else {
// 計數(shù)器,表示發(fā)現(xiàn)不是Deferred對象,而是普通JavaScript對象
--count;
}
}
// 計數(shù)器為0時,表示傳入的參數(shù)都不是Deferred對象
// 執(zhí)行成功回調(diào)函數(shù)隊列,上下文強制為傳入的第一個Deferred對象
if ( !count ) {
deferred.resolveWith( deferred, args );
}
// deferred !== firstParam,即deferred為新創(chuàng)建的Deferred對象
// 即length == 0
} else if ( deferred !== firstParam ) {
// 執(zhí)行成功回調(diào)函數(shù)隊列,上下文強制為新創(chuàng)建的Deferred對象
deferred.resolveWith( deferred, length ? [ firstParam ] : [] );
}
// 返回傳入的第一個Deferred或新創(chuàng)建的Deferred對象的只讀視圖
return deferred.promise();
}
5.6 Deferred應(yīng)用
l jQuery.ajax()
n TODO
5.7 可以學(xué)習(xí)的技巧
l 閉包
復(fù)制代碼 代碼如下:
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 遍歷動態(tài)數(shù)組的技巧
while( callbacks[ 0 ] ) {
callbacks.shift().apply( context, args );
}
l try/catch/finally 實現(xiàn)錯誤處理
語法
說明
try {
// tryStatements
} catch( exception ) {
// catchStatements
} finally {
// finallyStatements
}
tryStatements
必選項。
可能發(fā)生錯誤的語句。
exception
必選項。任何變量名。
exception 的初始化值是扔出的錯誤的值。
catchStatements
可選項。
處理在相關(guān)聯(lián)的 tryStatement 中發(fā)生的錯誤的語句。
finallyStatements
可選項。
在所有其他過程發(fā)生之后無條件執(zhí)行的語句。
l 鏈式對象:通過返回this實現(xiàn)鏈式調(diào)用
方法
返回值
done
this(即deferred)
resolveWith
this(即deferred)
resolve
this(即deferred)
cancel
this(即deferred)
l 代碼復(fù)用 $.each
jQuery.each( {
done: [ fnDone, "resolve" ], // done在后文中會指向deferred.done
fail: [ fnFail, "reject" ]
}, function( handler, data ) {
// 公共代碼復(fù)用
});
5.8 后續(xù)
l Deferred在jQuery中的應(yīng)用
l Deferred的自定義應(yīng)用
您可能感興趣的文章:
- jQuery的deferred對象使用詳解
- jQuery Deferred和Promise創(chuàng)建響應(yīng)式應(yīng)用程序詳細介紹
- 利用jQuery的deferred對象實現(xiàn)異步按順序加載JS文件
- jQuery之Deferred對象詳解
- jQuery $.extend()用法總結(jié)
- jQuery插件開發(fā)的兩種方法及$.fn.extend的詳解
- jQuery.extend 函數(shù)詳解
- 原生js實現(xiàn)復(fù)制對象、擴展對象 類似jquery中的extend()方法
- jQuery.extend()的實現(xiàn)方式詳解及實例
- jQuery中的deferred對象和extend方法詳解
相關(guān)文章
jQuery實現(xiàn)漂亮實用的商品圖片tips提示框效果(無圖片箭頭+陰影)
這篇文章主要介紹了jQuery實現(xiàn)漂亮實用的商品圖片tips提示框效果,具有鼠標滑過顯示動態(tài)提示框的效果,涉及針對鼠標事件的響應(yīng)及頁面元素動態(tài)操作技巧,需要的朋友可以參考下2016-04-04
jQuery Validation Plugin驗證插件手動驗證
jquery.validate是jquery旗下的一個驗證框架,借助jquery的優(yōu)勢,我們可以迅速驗證一些常見的輸入,并且可以自己擴充自己的驗證方法,并且對國際化也有很好的支持,接下來通過本文給大家介紹jQuery Validation Plugin驗證插件手動驗證2016-01-01

