摘要:回調(diào)隊(duì)列對(duì)象,用于構(gòu)建易于操作的回調(diào)函數(shù)集合,在操作完成后進(jìn)行執(zhí)行。對(duì)象對(duì)象,用于管理回調(diào)函數(shù)的多用途列表。如果傳入一個(gè)延遲對(duì)象,則返回該對(duì)象的對(duì)象,可以繼續(xù)綁定其余回調(diào),在執(zhí)行結(jié)束狀態(tài)之后也同時(shí)調(diào)用其回調(diào)函數(shù)。
在工作中我們可能會(huì)把jQuery選擇做自己項(xiàng)目的基礎(chǔ)庫(kù),因?yàn)槠涮峁┝撕?jiǎn)便的DOM選擇器以及封裝了很多實(shí)用的方法,比如$.ajax(),它使得我們不用操作xhr和xdr對(duì)象,直接書(shū)寫(xiě)我們的代碼邏輯即可。更為豐富的是它在ES6沒(méi)有原生支持的那段時(shí)間,提供了Deferred對(duì)象,類(lèi)似于Promise對(duì)象,支持done/fail/progress/always方法和when批處理方法,這可能在項(xiàng)目上幫助過(guò)你。
ES6提供了Promise對(duì)象,但由于它是內(nèi)置C++實(shí)現(xiàn)的,所以你也沒(méi)法看它的設(shè)計(jì)。不如我們通過(guò)jQuery的源碼來(lái)探究其設(shè)計(jì)思路,并比較一下兩者的區(qū)別。本文采用jquey-3.1.2.js版本,其中英文注釋為原版,中文注釋為我添加。
jQuery的ajax總體設(shè)計(jì)jQuery在內(nèi)部設(shè)置了全局的ajax參數(shù),在每一個(gè)ajax請(qǐng)求初始化時(shí),用傳遞的參數(shù)與默認(rèn)的全局參數(shù)進(jìn)行混合,并構(gòu)建一個(gè)jqXHR對(duì)象(提供比原生XHR更為豐富的方法,同時(shí)實(shí)現(xiàn)其原生方法),通過(guò)傳遞的參數(shù),來(lái)判斷其是否跨域、傳遞的參數(shù)類(lèi)型等,設(shè)置好相關(guān)頭部信息。同時(shí)其被初始化為一個(gè)內(nèi)置Deferred對(duì)象用于異步操作(后面講到),添加done/fail方法作為回調(diào)。同時(shí)我們也封裝了$.get/$.post方法來(lái)快捷調(diào)用$.ajax方法。
上面提到的Deferred對(duì)象,與ES6的Promise對(duì)象類(lèi)似,用于更為方便的異步操作,多種回調(diào)以及更好的書(shū)寫(xiě)方式。提供progress/fail/done方法,并分別用該對(duì)象的notify/reject/resolve方法觸發(fā),可以使用then方法快速設(shè)置三個(gè)方法,使用always添加都會(huì)執(zhí)行的回調(diào),并且提供when方法支持多個(gè)異步操作合并回調(diào)。可以追加不同的回調(diào)列表,其回調(diào)列表是使用內(nèi)部Callbacks對(duì)象,更方便的按照隊(duì)列的方式來(lái)進(jìn)行執(zhí)行。
Callbacks回調(diào)隊(duì)列對(duì)象,用于構(gòu)建易于操作的回調(diào)函數(shù)集合,在操作完成后進(jìn)行執(zhí)行。支持四種初始化的方式once/unique/memory/stopOnFalse,分別代表只執(zhí)行依次、去重、緩存結(jié)果、鏈?zhǔn)秸{(diào)用支持終止。提供fired/locked/disabled狀態(tài)值,代表是否執(zhí)行過(guò)、上鎖、禁用。提供add/remove/empty/fire/lock/disable方法操作回調(diào)函數(shù)隊(duì)列。
主要涉及到的概念就是這三個(gè),不再做延伸,三個(gè)對(duì)象的設(shè)計(jì)代碼行數(shù)在1200行左右,斷斷續(xù)續(xù)看了我一周 (′?`」 ∠) 。我們從這三個(gè)倒序開(kāi)始入手剖析其設(shè)計(jì)。
jQuery.Callbacks對(duì)象Callbacks對(duì)象,用于管理回調(diào)函數(shù)的多用途列表。它提供了六個(gè)主要方法:
add: 向列表中添加回調(diào)函數(shù)
remove: 移除列表中的回調(diào)函數(shù)
empty: 清空列表中的回調(diào)函數(shù)
fire: 依次執(zhí)行列表中的回調(diào)函數(shù)
lock: 對(duì)列表上鎖,禁止一切操作,清除數(shù)據(jù),但保留緩存的環(huán)境變量(只在memory參數(shù)時(shí)有用)
disable: 禁用該回調(diào)列表,所有數(shù)據(jù)清空
在初始化時(shí),支持四個(gè)參數(shù),用空格分割:
once: 該回調(diào)列表只執(zhí)行依次
memory: 緩存執(zhí)行環(huán)境,在添加新回調(diào)時(shí)執(zhí)行先執(zhí)行一次
unique: 去重,每一個(gè)函數(shù)均不同(指的是引用地址)
stopOnFalse: 在調(diào)用中,如果前一個(gè)函數(shù)返回false,中斷列表的后續(xù)執(zhí)行
我們來(lái)看下其實(shí)例使用:
let cl = $.Callbacks("once memory unique stopOnFalse");
fn1 = function (data) {
console.log(data);
};
fn2 = function (data) {
console.log("fn2 say:", data);
return false;
};
cl.add(fn1);
cl.fire("Nicholas"); // Nicholas
// 由于我們使用memory參數(shù),保存了執(zhí)行環(huán)境,在添加新的函數(shù)時(shí)自動(dòng)執(zhí)行一次
cl.add(fn2); // fn2 say: Nicholas
// 由于我們使用once參數(shù),所以只能執(zhí)行(fire)一次,此處無(wú)任何輸出
cl.fire("Lee");
// 后面我們假設(shè)這里沒(méi)有傳入once參數(shù),每次fire都可以執(zhí)行
cl.fire("Lee"); // Lee fn2 say: Lee
// 清空列表
cl.empty();
cl.add(fn2, fn1);
// 由于我們?cè)O(shè)置了stopOnFalse,而fn2返回了false,則后添加的fn1不會(huì)執(zhí)行
cl.fire("Nicholas"); // fn2 say: Nicholas
// 上鎖cl,禁用其操作,清除數(shù)據(jù),但是我們添加了memory參數(shù),它依然會(huì)對(duì)后續(xù)添加的執(zhí)行一次
cl.lock();
// 無(wú)響應(yīng)
cl.fire();
cl.add(fn2); // fn2 say: Nicholas
// 禁用cl,禁止一切操作,清除數(shù)據(jù)
cl.disable();
除了上面所說(shuō)的主要功能,還提供has/locked/disabled/fireWith/fired等輔助函數(shù)。
其所有源碼實(shí)現(xiàn)及注釋為:
jQuery.Callbacks = function( options ) {
options = typeof options === "string" ?
// 將字符串中空格分割的子串,轉(zhuǎn)換為值全為true的對(duì)象屬性
createOptions( options ) :
jQuery.extend( {}, options );
var // Flag to know if list is currently firing
firing,
// Last fire value for non-forgettable lists
memory,
// Flag to know if list was already fired
fired,
// Flag to prevent firing
locked,
// Actual callback list
list = [],
// Queue of execution data for repeatable lists
queue = [],
// Index of currently firing callback (modified by add/remove as needed)
firingIndex = -1,
// Fire callbacks
fire = function() {
// Enforce single-firing
locked = locked || options.once;
// Execute callbacks for all pending executions,
// respecting firingIndex overrides and runtime changes
fired = firing = true;
// 為quene隊(duì)列中不同的[context, args]執(zhí)行l(wèi)ist回調(diào)列表,執(zhí)行過(guò)程中會(huì)判斷stopOnFalse中間中斷
for ( ; queue.length; firingIndex = -1 ) {
memory = queue.shift();
while ( ++firingIndex < list.length ) {
// Run callback and check for early termination
if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&
options.stopOnFalse ) {
// Jump to end and forget the data so .add doesn"t re-fire
firingIndex = list.length;
memory = false;
}
}
}
// Forget the data if we"re done with it
if ( !options.memory ) {
memory = false;
}
firing = false;
// Clean up if we"re done firing for good
// 如果不再執(zhí)行了,就將保存回調(diào)的list清空,對(duì)內(nèi)存更好
if ( locked ) {
// Keep an empty list if we have data for future add calls
if ( memory ) {
list = [];
// Otherwise, this object is spent
} else {
list = "";
}
}
},
// Actual Callbacks object
self = {
// Add a callback or a collection of callbacks to the list
add: function() {
if ( list ) {
// If we have memory from a past run, we should fire after adding
// 如果我們選擇緩存執(zhí)行環(huán)境,會(huì)在新添加回調(diào)時(shí)執(zhí)行一次保存的環(huán)境
if ( memory && !firing ) {
firingIndex = list.length - 1;
queue.push( memory );
}
( function add( args ) {
jQuery.each( args, function( _, arg ) {
// 如果是函數(shù),則判斷是否去重,如果為類(lèi)數(shù)組,則遞歸執(zhí)行該內(nèi)部函數(shù)
if ( jQuery.isFunction( arg ) ) {
if ( !options.unique || !self.has( arg ) ) {
list.push( arg );
}
} else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) {
// Inspect recursively
add( arg );
}
} );
} )( arguments );
if ( memory && !firing ) {
fire();
}
}
return this;
},
// Remove a callback from the list
// 移除所有的相同回調(diào),并同步將firingIndex-1
remove: function() {
jQuery.each( arguments, function( _, arg ) {
var index;
while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
list.splice( index, 1 );
// Handle firing indexes
if ( index <= firingIndex ) {
firingIndex--;
}
}
} );
return this;
},
// Check if a given callback is in the list.
// If no argument is given, return whether or not list has callbacks attached.
// 檢查是否存在該函數(shù),如果不傳遞參數(shù),則返回是否有回調(diào)函數(shù)
has: function( fn ) {
return fn ?
jQuery.inArray( fn, list ) > -1 :
list.length > 0;
},
// Remove all callbacks from the list
empty: function() {
if ( list ) {
list = [];
}
return this;
},
// Disable .fire and .add
// Abort any current/pending executions
// Clear all callbacks and values
// 置locked為[],即!![] === true,同時(shí)將隊(duì)列和列表都清空,即禁用了該回調(diào)集合
disable: function() {
locked = queue = [];
list = memory = "";
return this;
},
disabled: function() {
return !list;
},
// Disable .fire
// Also disable .add unless we have memory (since it would have no effect)
// Abort any pending executions
// 不允許執(zhí)行,但如果有緩存,則我們?cè)试S添加后在緩存的環(huán)境下執(zhí)行新添加的回調(diào)
lock: function() {
locked = queue = [];
if ( !memory && !firing ) {
list = memory = "";
}
return this;
},
locked: function() {
return !!locked;
},
// Call all callbacks with the given context and arguments
// 為fire附帶了一個(gè)上下文來(lái)調(diào)用fire函數(shù),
fireWith: function( context, args ) {
if ( !locked ) {
args = args || [];
args = [ context, args.slice ? args.slice() : args ];
queue.push( args );
if ( !firing ) {
fire();
}
}
return this;
},
// Call all the callbacks with the given arguments
fire: function() {
self.fireWith( this, arguments );
return this;
},
// To know if the callbacks have already been called at least once
fired: function() {
return !!fired;
}
};
return self;
};
jQuery.Deferred對(duì)象
jQuery.Deferred對(duì)象是一個(gè)工廠函數(shù),返回一個(gè)用于異步或同步調(diào)用的deferred對(duì)象,支持鏈?zhǔn)秸{(diào)用、回調(diào)函數(shù)隊(duì)列,并且能針對(duì)返回的狀態(tài)不同執(zhí)行不同的回調(diào)。它類(lèi)似于ES6提供的Promise對(duì)象,提供9個(gè)主要的方法:
done: 操作成功響應(yīng)時(shí)的回調(diào)函數(shù)(同步或異步,以下相同)
fail: 操作失敗響應(yīng)時(shí)的回調(diào)函數(shù)
progress: 操作處理過(guò)程中的回調(diào)函數(shù)
resolve: 通過(guò)該方法解析該操作為成功狀態(tài),調(diào)用done
reject: 通過(guò)該方法解析該操作為失敗狀態(tài),調(diào)用fail
notify: 通過(guò)該方法解析該操作為執(zhí)行過(guò)程中,調(diào)用progress
then: 設(shè)置回調(diào)的簡(jiǎn)寫(xiě),接收三個(gè)參數(shù),分別是done/fail/progress
always: 設(shè)置必須執(zhí)行的回調(diào),無(wú)論是done還是fail
promise: 返回一個(gè)受限制的Deferred對(duì)象,不允許外部直接改變完成狀態(tài)
它的實(shí)現(xiàn)思想是創(chuàng)建一個(gè)對(duì)象,包含不同狀態(tài)下回調(diào)函數(shù)的隊(duì)列,并在狀態(tài)為失敗或成功后不允許再次改變。通過(guò)返回的Deferred對(duì)象進(jìn)行手動(dòng)調(diào)用resolve/reject/notify方法來(lái)控制流程。
看一個(gè)實(shí)例(純屬胡扯,不要當(dāng)真)。我們需要從間諜衛(wèi)星返回的數(shù)據(jù)用不同的算法來(lái)進(jìn)行解析,如果解析結(jié)果信號(hào)強(qiáng)度大于90%,則證明該數(shù)據(jù)有效,可以被解析;如果強(qiáng)度小于10%,則證明只是宇宙噪音;否則,證明數(shù)據(jù)可能有效,換一種算法解析:
// 我們封裝Deferred產(chǎn)生一個(gè)promise對(duì)象,其不能被外部手動(dòng)解析,只能內(nèi)部確定最終狀態(tài)
asynPromise = function () {
let d = $.Deferred();
(function timer() {
setTimeout(function () {
// 產(chǎn)生隨機(jī)數(shù),代替解析結(jié)果,來(lái)確定本次的狀態(tài)
let num = Math.random();
if (num > 0.9) {
d.resolve(); // 解析成功
} else if (num < 0.1) {
d.reject(); // 解析失敗
} else {
d.notify(); // 解析過(guò)程中
}
setTimeout(timer, 1000); // 持續(xù)不斷的解析數(shù)據(jù)
}, 1000);
})();
// 如果不返回promise對(duì)象,則可以被外部手動(dòng)調(diào)整解析狀態(tài)
return d.promise();
};
// then方法的三個(gè)參數(shù)分別代表完成、失敗、過(guò)程中的回調(diào)函數(shù)
asynPromise().then(function () {
console.log("resolve success");
}, function () {
console.log("reject fail");
}, function () {
console.log("notify progress");
});
// 本地執(zhí)行結(jié)果(每個(gè)人的不一樣,隨機(jī)分布,但最后一個(gè)一定是success或fail)
notify progress
notify progress
notify progress
notify progress
notify progress
reject fail // 后面不會(huì)再有輸出,因?yàn)橐坏┙馕鰻顟B(tài)為success或fail,則不會(huì)再改變
除了上面的主要功能,還提供了notifyWith/resolveWith/rejectWith/state輔助方法。
其所有的源碼實(shí)現(xiàn)和注釋為:
Deferred: function( func ) {
var tuples = [
// action, add listener, callbacks,
// ... .then handlers, argument index, [final state]
// 用于后面進(jìn)行第一個(gè)參數(shù)綁定調(diào)用第二個(gè)參數(shù),第三個(gè)和第四個(gè)參數(shù)分別是其不同的回調(diào)函數(shù)隊(duì)列
[ "notify", "progress", jQuery.Callbacks( "memory" ),
jQuery.Callbacks( "memory" ), 2 ],
[ "resolve", "done", jQuery.Callbacks( "once memory" ),
jQuery.Callbacks( "once memory" ), 0, "resolved" ],
[ "reject", "fail", jQuery.Callbacks( "once memory" ),
jQuery.Callbacks( "once memory" ), 1, "rejected" ]
],
state = "pending",
promise = {
state: function() {
return state;
},
// 同時(shí)添加done和fail句柄
always: function() {
deferred.done( arguments ).fail( arguments );
return this;
},
"catch": function( fn ) {
return promise.then( null, fn );
},
then: function( onFulfilled, onRejected, onProgress ) {
var maxDepth = 0;
function resolve( depth, deferred, handler, special ) {
return function() {
var that = this,
args = arguments,
mightThrow = function() {
var returned, then;
// Support: Promises/A+ section 2.3.3.3.3
// https://promisesaplus.com/#point-59
// Ignore double-resolution attempts
if ( depth < maxDepth ) {
return;
}
returned = handler.apply( that, args );
// Support: Promises/A+ section 2.3.1
// https://promisesaplus.com/#point-48
if ( returned === deferred.promise() ) {
throw new TypeError( "Thenable self-resolution" );
}
// Support: Promises/A+ sections 2.3.3.1, 3.5
// https://promisesaplus.com/#point-54
// https://promisesaplus.com/#point-75
// Retrieve `then` only once
then = returned &&
// Support: Promises/A+ section 2.3.4
// https://promisesaplus.com/#point-64
// Only check objects and functions for thenability
( typeof returned === "object" ||
typeof returned === "function" ) &&
returned.then;
// Handle a returned thenable
if ( jQuery.isFunction( then ) ) {
// Special processors (notify) just wait for resolution
if ( special ) {
then.call(
returned,
resolve( maxDepth, deferred, Identity, special ),
resolve( maxDepth, deferred, Thrower, special )
);
// Normal processors (resolve) also hook into progress
} else {
// ...and disregard older resolution values
maxDepth++;
then.call(
returned,
resolve( maxDepth, deferred, Identity, special ),
resolve( maxDepth, deferred, Thrower, special ),
resolve( maxDepth, deferred, Identity,
deferred.notifyWith )
);
}
// Handle all other returned values
} else {
// Only substitute handlers pass on context
// and multiple values (non-spec behavior)
if ( handler !== Identity ) {
that = undefined;
args = [ returned ];
}
// Process the value(s)
// Default process is resolve
( special || deferred.resolveWith )( that, args );
}
},
// Only normal processors (resolve) catch and reject exceptions
// 只有普通的process能處理異常,其余的要進(jìn)行捕獲,這里不是特別明白,應(yīng)該是因?yàn)闆](méi)有改最終的狀態(tài)吧
process = special ?
mightThrow :
function() {
try {
mightThrow();
} catch ( e ) {
if ( jQuery.Deferred.exceptionHook ) {
jQuery.Deferred.exceptionHook( e,
process.stackTrace );
}
// Support: Promises/A+ section 2.3.3.3.4.1
// https://promisesaplus.com/#point-61
// Ignore post-resolution exceptions
if ( depth + 1 >= maxDepth ) {
// Only substitute handlers pass on context
// and multiple values (non-spec behavior)
if ( handler !== Thrower ) {
that = undefined;
args = [ e ];
}
deferred.rejectWith( that, args );
}
}
};
// Support: Promises/A+ section 2.3.3.3.1
// https://promisesaplus.com/#point-57
// Re-resolve promises immediately to dodge false rejection from
// subsequent errors
if ( depth ) {
process();
} else {
// Call an optional hook to record the stack, in case of exception
// since it"s otherwise lost when execution goes async
if ( jQuery.Deferred.getStackHook ) {
process.stackTrace = jQuery.Deferred.getStackHook();
}
window.setTimeout( process );
}
};
}
return jQuery.Deferred( function( newDefer ) {
// progress_handlers.add( ... )
tuples[ 0 ][ 3 ].add(
resolve(
0,
newDefer,
jQuery.isFunction( onProgress ) ?
onProgress :
Identity,
newDefer.notifyWith
)
);
// fulfilled_handlers.add( ... )
tuples[ 1 ][ 3 ].add(
resolve(
0,
newDefer,
jQuery.isFunction( onFulfilled ) ?
onFulfilled :
Identity
)
);
// rejected_handlers.add( ... )
tuples[ 2 ][ 3 ].add(
resolve(
0,
newDefer,
jQuery.isFunction( onRejected ) ?
onRejected :
Thrower
)
);
} ).promise();
},
// Get a promise for this deferred
// If obj is provided, the promise aspect is added to the object
// 通過(guò)該promise對(duì)象返回一個(gè)新的擴(kuò)展promise對(duì)象或自身
promise: function( obj ) {
return obj != null ? jQuery.extend( obj, promise ) : promise;
}
},
deferred = {};
// Add list-specific methods
// 給promise添加done/fail/progress事件,并添加互相的影響關(guān)系,并為deferred對(duì)象添加3個(gè)事件函數(shù)notify/resolve/reject
jQuery.each( tuples, function( i, tuple ) {
var list = tuple[ 2 ],
stateString = tuple[ 5 ];
// promise.progress = list.add
// promise.done = list.add
// promise.fail = list.add
promise[ tuple[ 1 ] ] = list.add;
// Handle state
// 只有done和fail有resolved和rejected狀態(tài)字段,給兩個(gè)事件添加回調(diào),禁止再次done或者fail,鎖住progress不允許執(zhí)行回調(diào)
if ( stateString ) {
list.add(
function() {
// state = "resolved" (i.e., fulfilled)
// state = "rejected"
state = stateString;
},
// rejected_callbacks.disable
// fulfilled_callbacks.disable
tuples[ 3 - i ][ 2 ].disable,
// progress_callbacks.lock
tuples[ 0 ][ 2 ].lock
);
}
// progress_handlers.fire
// fulfilled_handlers.fire
// rejected_handlers.fire
// 執(zhí)行第二個(gè)回調(diào)列表
list.add( tuple[ 3 ].fire );
// deferred.notify = function() { deferred.notifyWith(...) }
// deferred.resolve = function() { deferred.resolveWith(...) }
// deferred.reject = function() { deferred.rejectWith(...) }
// 綁定notify/resolve/reject的事件,實(shí)際執(zhí)行的函數(shù)體為加入上下文的With函數(shù)
deferred[ tuple[ 0 ] ] = function() {
deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments );
return this;
};
// deferred.notifyWith = list.fireWith
// deferred.resolveWith = list.fireWith
// deferred.rejectWith = list.fireWith
deferred[ tuple[ 0 ] + "With" ] = list.fireWith;
} );
// Make the deferred a promise
// 將deferred擴(kuò)展為一個(gè)promise對(duì)象
promise.promise( deferred );
// Call given func if any
// 在創(chuàng)建前執(zhí)行傳入的回調(diào)函數(shù)進(jìn)行修改
if ( func ) {
func.call( deferred, deferred );
}
// All done!
return deferred;
},
jQuery.when方法
$.when()提供一種方法執(zhí)行一個(gè)或多個(gè)函數(shù)的回調(diào)函數(shù)。如果傳入一個(gè)延遲對(duì)象,則返回該對(duì)象的Promise對(duì)象,可以繼續(xù)綁定其余回調(diào),在執(zhí)行結(jié)束狀態(tài)之后也同時(shí)調(diào)用其when回調(diào)函數(shù)。如果傳入多個(gè)延遲對(duì)象,則返回一個(gè)新的master延遲對(duì)象,跟蹤所有的聚集狀態(tài),如果都成功解析完成,才調(diào)用其when回調(diào)函數(shù);如果有一個(gè)失敗,則全部失敗,執(zhí)行錯(cuò)誤回調(diào)。
其使用方法:
$.when($.ajax("/page1.php"), $.ajax("/page2.php"))
.then(myFunc, myFailure);
其所有源碼實(shí)現(xiàn)和注釋為(能力有限,有些地方實(shí)在不能準(zhǔn)確理解執(zhí)行流程):
// 給when傳遞的對(duì)象綁定master.resolve和master.reject,用于聚集多異步對(duì)象的狀態(tài)
function adoptValue( value, resolve, reject, noValue ) {
var method;
try {
// Check for promise aspect first to privilege synchronous behavior
// 如果when傳入的參數(shù)promise方法可用,則封裝promise并添加done和fail方法調(diào)用resolve和reject
if ( value && jQuery.isFunction( ( method = value.promise ) ) ) {
method.call( value ).done( resolve ).fail( reject );
// Other thenables
// 否則,就判斷傳入?yún)?shù)的then方法是否可用,如果可用就傳入resolve和reject方法
} else if ( value && jQuery.isFunction( ( method = value.then ) ) ) {
method.call( value, resolve, reject );
// Other non-thenables
// 如果均不可用,則為非異步對(duì)象,直接resolve解析原值
} else {
// Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer:
// * false: [ value ].slice( 0 ) => resolve( value )
// * true: [ value ].slice( 1 ) => resolve()
resolve.apply( undefined, [ value ].slice( noValue ) );
}
// For Promises/A+, convert exceptions into rejections
// Since jQuery.when doesn"t unwrap thenables, we can skip the extra checks appearing in
// Deferred#then to conditionally suppress rejection.
} catch ( value ) {
// Support: Android 4.0 only
// Strict mode functions invoked without .call/.apply get global-object context
// 一個(gè)安卓4.0的bug,這里不做闡釋
reject.apply( undefined, [ value ] );
}
}
// Deferred helper
when: function( singleValue ) {
var
// count of uncompleted subordinates
remaining = arguments.length,
// count of unprocessed arguments
i = remaining,
// subordinate fulfillment data
resolveContexts = Array( i ),
resolveValues = slice.call( arguments ),
// the master Deferred
master = jQuery.Deferred(),
// subordinate callback factory
// 將每一個(gè)響應(yīng)的環(huán)境和值都保存到列表里,在全部完成后統(tǒng)一傳給主Promise用于執(zhí)行
updateFunc = function( i ) {
return function( value ) {
resolveContexts[ i ] = this;
resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
if ( !( --remaining ) ) {
master.resolveWith( resolveContexts, resolveValues );
}
};
};
// Single- and empty arguments are adopted like Promise.resolve
// 如果只有一個(gè)參數(shù),則直接將其作為master的回調(diào)
if ( remaining <= 1 ) {
adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject,
!remaining );
// Use .then() to unwrap secondary thenables (cf. gh-3000)
if ( master.state() === "pending" ||
jQuery.isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) {
return master.then();
}
}
// Multiple arguments are aggregated like Promise.all array elements
// 多參數(shù)時(shí),進(jìn)行所有參數(shù)的解析狀態(tài)聚合到master上
while ( i-- ) {
adoptValue( resolveValues[ i ], updateFunc( i ), master.reject );
}
return master.promise();
}
后續(xù)
本來(lái)想把jQuery.Deferred和jQuery.ajax以及ES6的Promise對(duì)象給統(tǒng)一講一下,結(jié)果發(fā)現(xiàn)牽涉的東西太多,每一個(gè)都可以多帶帶寫(xiě)一篇文章,怕大家說(shuō)太長(zhǎng)不看,這里先寫(xiě)第一部分jQuery.Deferred吧,后續(xù)再補(bǔ)充另外兩篇。
看jQuery的文檔很容易,使用也很方便,但其實(shí)真正想要講好很復(fù)雜,更不要說(shuō)寫(xiě)篇源碼分析文章了。真的是努力理解設(shè)計(jì)者的思路,爭(zhēng)取每行都能理解邊界條件,但踩坑太少,應(yīng)用場(chǎng)景太少,確實(shí)有很大的疏漏,希望大家能夠理解,不要偏聽(tīng)一面之詞。
參考資料jQuery - Callbacks: http://api.jquery.com/jQuery....
segment - jQuery Callbacks: https://segmentfault.com/a/11...
jQuery-3.2.1版本
jQuery - Deferred: http://api.jquery.com/jQuery....
jQuery - when: http://www.jquery123.com/jQue...
cnblogs - 搞懂jQuery的Promise: http://www.cnblogs.com/lvdaba...
Promise A+ 規(guī)范: http://malcolmyu.github.io/ma...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/89269.html
摘要:通常的做法是,為它們指定回調(diào)函數(shù)。請(qǐng)求返回請(qǐng)求返回請(qǐng)求返回異步隊(duì)列解耦異步任務(wù)和回調(diào)函數(shù)為模塊隊(duì)列模塊事件提供基礎(chǔ)功能。 前言 jQuery整體框架甚是復(fù)雜,也不易讀懂,這幾日一直在研究這個(gè)笨重而強(qiáng)大的框架。jQuery的總體架構(gòu)可以分為:入口模塊、底層模塊和功能模塊。這里,我們以jquery-1.7.1為例進(jìn)行分析。 jquery的總體架構(gòu) 16 (function( window,...
摘要:在服務(wù)器端,異步模式甚至是唯一的模式,因?yàn)閳?zhí)行環(huán)境是單線程的,如果允許同步執(zhí)行所有請(qǐng)求,服務(wù)器性能會(huì)急劇下降,很快就會(huì)失去響應(yīng)。第三是,捕捉不到他的錯(cuò)誤異步編程方法回調(diào)函數(shù)這是異步編程最基本的方法。 前言 你可能知道,Javascript語(yǔ)言的執(zhí)行環(huán)境是單線程(single thread)。所謂單線程,就是指一次只能完成一件任務(wù)。如果有多個(gè)任務(wù),就必須排隊(duì),前面一個(gè)任務(wù)完成,再執(zhí)行后面...
摘要:上篇文章中講到,使用的方法操作請(qǐng)求,會(huì)受到回調(diào)函數(shù)嵌套的問(wèn)題。第一次回調(diào)第二次回調(diào)內(nèi)部實(shí)現(xiàn)上,和都是基于實(shí)現(xiàn)的對(duì)于多個(gè)同時(shí)請(qǐng)求,共同執(zhí)行同一個(gè)回調(diào)函數(shù)這一點(diǎn)上,有一個(gè)方法,接受多個(gè)對(duì)象實(shí)例,同時(shí)執(zhí)行。 上篇文章中講到,使用jquery的ajax方法操作ajax請(qǐng)求,會(huì)受到回調(diào)函數(shù)嵌套的問(wèn)題。當(dāng)然,jquery團(tuán)隊(duì)也發(fā)現(xiàn)了這個(gè)問(wèn)題,在2011年,也就是jquery 1.5版本之后,jQu...
摘要:根據(jù)項(xiàng)目選型決定是否開(kāi)啟。為了壓縮,可維護(hù)為了支持從而使用代替變量存儲(chǔ)防沖突會(huì)用到,形如版本號(hào)聲明最終調(diào)用的是這個(gè)原型實(shí)際上。功能檢測(cè)統(tǒng)一兼容性問(wèn)題。 概覽 (function (){ (21 , 94) 定義了一些變量和函數(shù) jQuery=function(); (96 , 293) 給jQuery對(duì)象添加一些方法和屬性; (285 , 347) ...
摘要:根據(jù)項(xiàng)目選型決定是否開(kāi)啟。為了壓縮,可維護(hù)為了支持從而使用代替變量存儲(chǔ)防沖突會(huì)用到,形如版本號(hào)聲明最終調(diào)用的是這個(gè)原型實(shí)際上。功能檢測(cè)統(tǒng)一兼容性問(wèn)題。 概覽 (function (){ (21 , 94) 定義了一些變量和函數(shù) jQuery=function(); (96 , 293) 給jQuery對(duì)象添加一些方法和屬性; (285 , 347) ...
閱讀 3571·2021-11-12 10:36
閱讀 2973·2021-09-22 15:35
閱讀 2895·2021-09-04 16:41
閱讀 1251·2019-08-30 15:55
閱讀 3654·2019-08-29 18:43
閱讀 2141·2019-08-23 18:24
閱讀 1483·2019-08-23 18:10
閱讀 1980·2019-08-23 11:31