最細(xì)的實(shí)現(xiàn)剖析:jQuery 2.0.3源碼分析Deferred
Deferred的概念請看第一篇
http://www.cnblogs.com/aaronjs/p/3348569.html
******************構(gòu)建Deferred對象時(shí)候的流程圖**************************
**********************源碼解析**********************
因?yàn)閏allback被剝離出去后,整個(gè)deferred就顯得非常的精簡
- jQuery.extend({
- Deferred : function(){}
- when : function()
- )}
對于extend的繼承這個(gè)東東,在之前就提及過jquery如何處理內(nèi)部jquery與init相互引用this的問題
對于JQ的整體架構(gòu)一定要弄懂 http://www.cnblogs.com/aaronjs/p/3278578.html
所以當(dāng)jQuery.extend只有一個(gè)參數(shù)的時(shí)候,其實(shí)就是對jQuery靜態(tài)方法的一個(gè)擴(kuò)展
我們在具體看看2個(gè)靜態(tài)方法內(nèi)部都干了些什么:
Deferred整體結(jié)構(gòu):
源碼精簡了部分代碼
- Deferred: function( func ) {
- var tuples = [
- // action, add listener, listener list, final state
- [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
- [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
- [ "notify", "progress", jQuery.Callbacks("memory") ]
- ],
- state = "pending",
- promise = {
- state: function() {},
- always: function() {},
- then: function( /* fnDone, fnFail, fnProgress */ ) { },
- // Get a promise for this deferred
- // If obj is provided, the promise aspect is added to the object
- promise: function( obj ) {}
- },
- deferred = {};
- jQuery.each( tuples, function( i, tuple ) {
- deferred[ tuple[0] + "With" ] = list.fireWith;
- });
- promise.promise( deferred );
- // All done!
- return deferred;
- },
- 顯而易見Deferred是個(gè)工廠類,返回的是內(nèi)部構(gòu)建的deferred對象
- tuples 創(chuàng)建三個(gè)$.Callbacks對象,分別表示成功,失敗,處理中三種狀態(tài)
- 創(chuàng)建了一個(gè)promise對象,具有state、always、then、primise方法
- 擴(kuò)展primise對象生成最終的Deferred對象,返回該對象
這里其實(shí)就是3個(gè)處理,但是有個(gè)優(yōu)化代碼的地方,就是把共性的代碼給抽象出來,通過動(dòng)態(tài)生成了
具體源碼分析:
Deferred自身則圍繞這三個(gè)對象進(jìn)行更高層次的抽象
- 觸發(fā)回調(diào)函數(shù)列表執(zhí)行(函數(shù)名)
- 添加回調(diào)函數(shù)(函數(shù)名)
- 回調(diào)函數(shù)列表(jQuery.Callbacks對象)
- deferred最終狀態(tài)(第三組數(shù)據(jù)除外)
- var tuples = [
- // action, add listener, listener list, final state
- [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
- [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
- [ "notify", "progress", jQuery.Callbacks("memory") ]
- ],
這里抽象出2組陣營:
1組:回調(diào)方法/事件訂閱
- done,fail,progress
2組:通知方法/事件發(fā)布
- resolve,reject,notify,resolveWith,rejectWith,notifyWith
tuples 元素集 其實(shí)是把相同有共同特性的代碼的給合并成一種結(jié)構(gòu),然后通過一次處理
- jQuery.each( tuples, function( i, tuple ) {
- var list = tuple[ 2 ],
- stateString = tuple[ 3 ];
- promise[ tuple[1] ] = list.add;
- if ( stateString ) {
- list.add(function() {
- state = stateString;
- // [ reject_list | resolve_list ].disable; progress_list.lock
- }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
- }
- deferred[ tuple[0] ] = function() {
- deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
- return this;
- };
- deferred[ tuple[0] + "With" ] = list.fireWith;
- });
對于tuples的3條數(shù)據(jù)集是分2部分處理的
第一部分將回調(diào)函數(shù)存入
- promise[ tuple[1] ] = list.add;
其實(shí)就是給promise賦予3個(gè)回調(diào)函數(shù)
- promise.done = $.Callbacks("once memory").add
- promise.fail = $.Callbacks("once memory").add
- promise.progressl = $.Callbacks("memory").add
如果存在deferred最終狀態(tài)
默認(rèn)會(huì)預(yù)先向doneList,failList中的list添加三個(gè)回調(diào)函數(shù)
- if ( stateString ) {
- list.add(function() {
- // state = [ resolved | rejected ]
- state = stateString;
- // [ reject_list | resolve_list ].disable; progress_list.lock
- }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
- }
*************************************************************
這里有個(gè)小技巧
i ^ 1 按位異或運(yùn)算符
所以實(shí)際上第二個(gè)傳參數(shù)是1、0索引對調(diào)了,所以取值是failList.disable與doneList.disable
*************************************************************
通過stateString有值這個(gè)條件,預(yù)先向doneList,failList中的list添加三個(gè)回調(diào)函數(shù)
分別是:
- doneList : [changeState, failList.disable, processList.lock]
- failList : [changeState, doneList.disable, processList.lock]
- changeState 改變狀態(tài)的匿名函數(shù),deferred的狀態(tài),分為三種:pending(初始狀態(tài)), resolved(解決狀態(tài)), rejected(拒絕狀態(tài))
- 不論deferred對象最終是resolve(還是reject),在首先改變對象狀態(tài)之后,都會(huì)disable另一個(gè)函數(shù)列表failList(或者doneList)
- 然后lock processList保持其狀態(tài),最后執(zhí)行剩下的之前done(或者fail)進(jìn)來的回調(diào)函數(shù)
所以第一步最終都是圍繞這add方法
- done/fail/是list.add也就是callbacks.add,將回調(diào)函數(shù)存入回調(diào)對象中
第二部分很簡單,給deferred對象擴(kuò)充6個(gè)方法
- resolve/reject/notify 是 callbacks.fireWith,執(zhí)行回調(diào)函數(shù)
- resolveWith/rejectWith/notifyWith 是 callbacks.fireWith 隊(duì)列方法引用
最后合并promise到deferred
- promise.promise( deferred );
- jQuery.extend( obj, promise )
所以最終通過工廠方法Deferred構(gòu)建的異步對象帶的所有的方法了
return 內(nèi)部的deferred對象了
由此可見我們在
- var defer = $.Deferred(); //構(gòu)建異步對象
的時(shí)候,內(nèi)部的對象就有了4個(gè)屬性方法了
- deferred: Object
- always: function () {
- done: function () {
- fail: function () {
- notify: function () {
- notifyWith: function ( context, args ) {
- pipe: function ( /* fnDone, fnFail, fnProgress */ ) {
- progress: function () {
- promise: function ( obj ) {
- reject: function () {
- rejectWith: function ( context, args ) {
- resolve: function () {
- resolveWith: function ( context, args ) {
- state: function () {
- then: function ( /* fnDone, fnFail, fnProgress */ ) {
- promise: Object
- always: function () {
- done: function () {
- fail: function () {
- pipe: function ( /* fnDone, fnFail, fnProgress */ ) {
- progress: function () {
- promise: function ( obj ) {
- state: function () {
- then: function ( /* fnDone, fnFail, fnProgress */ ) {
- state: "pending"
- tuples: Array[3]
構(gòu)造圖
以上只是在初始化構(gòu)建的時(shí)候,我們往下看看動(dòng)態(tài)執(zhí)行時(shí)候的處理
#p#
*****************執(zhí)行期***********************
一個(gè)最簡單的demo為例子
- var d = $.Deferred();
- setTimeout(function(){
- d.resolve(22)
- },0);
- d.then(function(val){
- console.log(val);
- })
當(dāng)延遲對象被 resolved 時(shí),任何通過 deferred.then或deferred.done 添加的 doneCallbacks,都會(huì)被調(diào)用。回調(diào)函數(shù)的執(zhí)行順序和它們被添加的順序是一樣的。傳遞給 deferred.resolve() 的 args 參數(shù),會(huì)傳給每個(gè)回調(diào)函數(shù)。當(dāng)延遲對象進(jìn)入 resolved 狀態(tài)后,再添加的任何 doneCallbacks,當(dāng)它們被添加時(shí),就會(huì)被立刻執(zhí)行,并帶上傳入給 .resolve()的參數(shù)
換句話說,我們調(diào)用d.resolve(22) 就等于是調(diào)用。
匿名函數(shù)并傳入?yún)?shù)值 22
- function(val){
- console.log(val); //22
- }
當(dāng)前實(shí)際的使用中會(huì)有各種復(fù)雜的組合情況,但是整的外部調(diào)用流程就是這樣的
***************** resolve的實(shí)現(xiàn) *******************
我們回顧下,其實(shí)Deferred對象,內(nèi)部的實(shí)現(xiàn)還是Callbacks對象,只是在外面再封裝了一層API,供接口調(diào)用
- d.resolve(22)
實(shí)際上調(diào)用的就是通過這個(gè)代碼生成的
- deferred[ tuple[0] ] = function() {
- deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
- return this;
- };
- deferred[ tuple[0] + "With" ] = list.fireWith;
- deferred.resolveWith()
最終執(zhí)行的就是 list.fireWith
- callbacks.fireWith()
所以最終又回到回調(diào)對象callbacks中的私有方法fire()了
Callbacks會(huì)通過
把回調(diào)函數(shù)給注冊到內(nèi)部的list = []上,我們回來過看看
- d.then(function(val){
- console.log(val);
- })
***************** then的實(shí)現(xiàn) *******************
- then: function( /* fnDone, fnFail, fnProgress */ ) {
- var fns = arguments;
- return jQuery.Deferred(function( newDefer ) {
- jQuery.each( tuples, function( i, tuple ) {
- var action = tuple[ 0 ],
- fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
- // deferred[ done | fail | progress ] for forwarding actions to newDefer
- deferred[ tuple[1] ](function() {
- //省略............
- });
- });
- fns = null;
- }).promise();
- },
- 遞歸jQuery.Deferred
- 傳遞了func
- 鏈?zhǔn)秸{(diào)用了promise()
因?yàn)樵诋惒綄ο蟮姆椒ǘ际乔短渍易饔糜驅(qū)傩苑椒ǖ?/strong>
這里我額外的提及一下作用域
- var d = $.Deferred();
這個(gè)異步對象d是作用域是如何呢?
第一層:無可爭議,瀏覽器環(huán)境下最外層是 window
第二層:jquery本身是一個(gè)閉包
第三層: Deferred工廠方法產(chǎn)生的作用域
如果用d.then()方法呢?
很明顯then方法又是嵌套在內(nèi)部的函數(shù),所以執(zhí)行的時(shí)候都默認(rèn)會(huì)包含以上三層作用域+自己本身函數(shù)產(chǎn)生的作用域了
我們用個(gè)簡單圖描繪下
根據(jù)規(guī)則,在最內(nèi)部的函數(shù)能夠訪問上層作用域的所有的變量
我們先從使用的層面去考慮下結(jié)構(gòu)設(shè)計(jì):
demo 1
- var defer = $.Deferred();
- var filtered = defer.then(function( value ) {
- return value * 2;
- });
- defer.resolve( 5 );
- filtered.done(function( value ) {
- console.log(value) //10
- });
demo 2
- var defer = $.Deferred();
- defer.then(function(value) {
- return value * 2;
- }).then(function(value) {
- return value * 2;
- }).done(function(value) {
- alert(value) //20
- });
- defer.resolve( 5 );
其實(shí)這里就是涉及到defer.then().then().done() 鏈?zhǔn)秸{(diào)用了
API是這么定義的:
- deferred.then( doneFilter [, failFilter ] [, progressFilter ] )
從jQuery 1.8開始, 方法返回一個(gè)新的promise(承諾),通過一個(gè)函數(shù),可以過濾deferred(延遲)的狀態(tài)和值。替換現(xiàn)在過時(shí)的deferred.pipe()方法。 doneFilter 和 failFilter函數(shù)過濾原deferred(延遲)的解決/拒絕的狀態(tài)和值。 progressFilter 函數(shù)過濾器的任何調(diào)用到原有的deferred(延遲)的notify 和 notifyWith的方法。 這些過濾器函數(shù)可以返回一個(gè)新的值傳遞給的 promise(承諾)的.done() 或 .fail() 回調(diào),或他們可以返回另一個(gè)觀察的對象(遞延,承諾等)傳遞給它的解決/拒絕的狀態(tài)和值promise(承諾)的回調(diào)。 如果過濾函數(shù)是空,或沒有指定,promise(承諾)將得到與原來值相同解決(resolved)或拒絕(rejected)。
我們抓住幾點(diǎn):
- 返回的是新的promise對象
- 內(nèi)部有一個(gè)濾器函數(shù)
從demo 1中我們就能看到
經(jīng)過x.then()方法處理的代碼中返回的this(filtered ),不是原來的$.Deferred()所有產(chǎn)生的那個(gè)異步對象(defer )了
#p#
所以,每經(jīng)過一個(gè)then那么內(nèi)部處理的this都要被重新設(shè)置,那么為什么要這樣處理呢?
源碼
- then: function( /* fnDone, fnFail, fnProgress */ ) {
- var fns = arguments;
- //分別為deferred的三個(gè)callbacklist添加回調(diào)函數(shù),根據(jù)fn的是否是函數(shù),分為兩種情況
- return jQuery.Deferred(function( newDefer ) {
- jQuery.each( tuples, function( i, tuple ) {
- var action = tuple[ 0 ],
- fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
- // deferred[ done | fail | progress ] for forwarding actions to newDefer
- deferred[ tuple[1] ](function() {
- var returned = fn && fn.apply( this, arguments );
- if ( returned && jQuery.isFunction( returned.promise ) ) {
- returned.promise()
- .done( newDefer.resolve )
- .fail( newDefer.reject )
- .progress( newDefer.notify );
- } else {
- newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
- }
- });
- });
- fns = null;
- }).promise();
- },
在Deferred傳遞實(shí)參的時(shí)候,支持一個(gè)flag,jQuery.Deferred(func)
傳遞一個(gè)回調(diào)函數(shù)
- // Call given func if any
- if ( func ) {
- func.call( deferred, deferred );
- }
所以newDefer可以看作是
- newDefer = $.Deferred();
那么func回調(diào)的處理的就是過濾函數(shù)了
- deferred[ tuple[1] ](function() {
- var returned = fn && fn.apply( this, arguments );
- if ( returned && jQuery.isFunction( returned.promise ) ) {
- returned.promise()
- .done( newDefer.resolve )
- .fail( newDefer.reject )
- .progress( newDefer.notify );
- } else {
- newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
- }
- });
這里其實(shí)也有編譯函數(shù)的概念,講未來要執(zhí)行的代碼,預(yù)先通過閉包函數(shù)也保存起來,使其訪問各自的作用域
第一步
分解tuples元素集
- jQuery.each( tuples, function( i, tuple ) {
- //過濾函數(shù)第一步處理
- })
第二步
分別為deferred[ done | fail | progress ]執(zhí)行對應(yīng)的add方法,增加過濾函數(shù)給done | fail | progress 方法
- deferred[ tuple[1] ](
- 傳入過濾函數(shù)
- )//過濾函數(shù) 執(zhí)行的時(shí)候在分解
代碼即
- deferred[done] = list.add = callback.add
第三步
返回return jQuery.Deferred().promise()
此時(shí)構(gòu)建了一個(gè)新的Deferred對象,但是返回的的是經(jīng)過promise()方法處理后的,返回的是一個(gè)受限的promise對象
所以整個(gè)then方法就處理了2個(gè)事情
- 構(gòu)建一個(gè)新的deferred對象,返回受限的promise對象
- 給父deferred對象的[ done | fail | progress ]方法都增加一個(gè)過濾函數(shù)的方法
我們知道defer.then方法返回的是一個(gè)新的jQuery.Deferred().promise()對象
那么我們把defer.then返回的稱之為子對象,那么如何與父對象var defer = $.Deferred() 關(guān)聯(lián)的起來的
我看看源碼
- deferred[ tuple[1] ](//過濾函數(shù)//)
deferred其實(shí)就是根級父對象的引用,所以就嵌套再深,其實(shí)都是調(diào)用了父對象deferred[ done | fail | progress 執(zhí)行add罷了
從圖中就能很明顯的看到 2個(gè)不同的deferred對象中 done fail progress分別都保存了不同的處理回調(diào)了
deferred.resolve( args )
- 當(dāng)延遲對象被 resolved 時(shí),任何通過 deferred.then或deferred.done 添加的 doneCallbacks,都會(huì)被調(diào)用
- 回調(diào)函數(shù)的執(zhí)行順序和它們被添加的順序是一樣的
- 傳遞給 deferred.resolve() 的 args 參數(shù),會(huì)傳給每個(gè)回調(diào)函數(shù)
- 當(dāng)延遲對象進(jìn)入 resolved 狀態(tài)后,再添加的任何 doneCallbacks,當(dāng)它們被添加時(shí),就會(huì)被立刻執(zhí)行,并帶上傳入給.resolve()的參數(shù)
流程如圖
流程解析:
1 執(zhí)行fire()方法,遞歸執(zhí)行l(wèi)ist所有包含的處理方法
2 執(zhí)行了默認(rèn)的 changeState, disable, lock 方法、
3 執(zhí)行過濾函數(shù)
根據(jù) var returned = fn.apply( this, arguments )的返回值(稱作returnReferred)是否是deferred對象
-
返回值是deferred對象,那么在returnReferred對象的三個(gè)回調(diào)函數(shù)列表中添加newDeferred的resolve(reject,notify)方法,也就是說newDeferrred的執(zhí)行依賴returnDeferred的狀態(tài)
-
不是函數(shù)的情況(如值為undefined或者null等),直接鏈接到newDeferred的resolve(reject,notify) 方法,也就是說 newDeferrred的執(zhí)行依賴外層的調(diào)用者deferred的狀態(tài)或者說是執(zhí)行動(dòng)作(resolve還是reject或者是notify) 此時(shí)deferred.then()相當(dāng)于將自己的callbacklist和newDeferred的callbacklist連接起來
下面就是嵌套deferred對象的劃分了
源碼還是要靠自己去折騰的,思想的提高比較難的,我們可以借鑒設(shè)計(jì)的思路,代碼書寫方式都是有益無害的。
流程的分析已經(jīng)比較透徹了,下一章在講解when的實(shí)現(xiàn)。