jquery1.83 之前所有与异步列队相关的模块详细介绍


Posted in Javascript onNovember 13, 2012

jQuery在1.5引入了Deferred对象(异步列队),当时它还没有划分为一个模块,放到核心模块中。直到1.52才分割出来。它拥有三个方法:_Deferred, Deferred与when。

出于变量在不同作用域的共用,jQuery实现异步列队时不使用面向对象方式,它把_Deferred当作一个工厂方法,返回一个不透明的函数列队。之所以说不透明,是因为它的状态与元素都以闭包手段保护起来,只能通过列队对象提供的方法进行操作。这几个方法分别是done(添加函数),resolveWith(指定作用域地执行所有函数),resolve(执行所有函数),isResolved(判定是否已经调用过resolveWith或resolve方法),cancel(中断执行操作)。但_Deferred自始至终都作为一个内部方法,从没有在文档中公开过。

Deferred在1.5是两个_Deferred的合体,但1+1不等于2,它还是做了增强。偷偷爆料,Deferred本来是python世界大名鼎鼎的Twisted框架的东西,由早期七大JS类库中的MochiKit取经回来,最后被dojo继承衣钵。jQuery之所以这样构造Deferred,分明不愿背抄袭的恶名,于是方法改得一塌糊涂,是jQuery命名最差的API,完全不知所云。它还加入当时正在热烈讨论的promise机制。下面是一个比较列表:

dojo jQuery 注解
addBoth then 同时添加正常回调与错误回调
addCallback done 添加正常回调
addErrback fail 添加错误回调
callback done 执行所有正常回调
errback reject 执行所有错误回调
doneWith 在指定作用域下执行所有正常回调,但dojo已经在addCallback上指定好了
rejectWith 在指定作用域下执行所有错误回调,但dojo已经在addErrback上指定好了
promise 返回一个外界不能改变其状态的Deferred对象(外称为Promise对象)

jQuery的when方法用于实现回调的回调,或者说,几个异列列队都执行后才执行另外的一些回调。这些后来的回调也是用done, when, fail添加的,但when返回的这个对象已经添加让用户控制它执行的能力了。因为这时它是种叫Promise的东西,只负责添加回调与让用户窥探其状态。一旦前一段回调都触发了,它就自然进入正常回调列队(deferred ,见Deferred方法的定义)或错误回调列队(failDeferred )中去。不过我这样讲,对于没有异步编程经验的人来说,肯定听得云里雾里。看实例好了。
$.when({aa:1}, {aa:2}).done(function(a,b){ 
console.log(a.aa) 
console.log(b.aa) 
});

直接输出1,2。如果是传入两个函数,也是返回两个函数。因此对于普通的数据类型,前面的when有多少个参数,后面的done, fail方法的回调就有多少个参数。
function fn(){ 
return 4; 
} 
function log(s){ 
window.console && console.log(s) 
} 
$.when( { num:1 }, 2, '3', fn() ).done(function(o1, o2, o3, o4){ 
log(o1.num); 
log(o2); 
log(o3); 
log(o4); 
});

如果我们想得到各个异步的结果,我们需要用resolve, resolveWith, reject, rejectWith进行传递它们。
var log = function(msg){ 
window.console && console.log(msg) 
} 
function asyncThing1(){ 
var dfd = $.Deferred(); 
setTimeout(function(){ 
log('asyncThing1 seems to be done...'); 
dfd.resolve('1111'); 
},1000); 
return dfd.promise(); 
} 
function asyncThing2(){ 
var dfd = $.Deferred(); 
setTimeout(function(){ 
log('asyncThing2 seems to be done...'); 
dfd.resolve('222'); 
},1500); 
return dfd.promise(); 
} 
function asyncThing3(){ 
var dfd = $.Deferred(); 
setTimeout(function(){ 
log('asyncThing3 seems to be done...'); 
dfd.resolve('333'); 
},2000); 
return dfd.promise(); 
} 
/* do it */ 
$.when( asyncThing1(), asyncThing2(), asyncThing3() ).done(function(res1, res2, res3){ 
log('all done!'); 
log(res1 + ', ' + res2 + ', ' + res3); 
})

异步列队一开始没什么人用(现在也没有什么人用,概念太抽象了,方法名起得太烂了),于是它只能在内部自产自销。首先被染指的是queue。queue模块是1.4为吸引社区的delay插件,特地从data模块中分化的产物,而data则是从event模块化分出来的。jQuery新模块的诞生总是因为用户对已有API的局限制不满而致。最早的queue模块的源码:
jQuery.extend({ 
queue: function( elem, type, data ) { 
if ( !elem ) { 
return; 
} 
type = (type || "fx") + "queue"; 
var q = jQuery.data( elem, type ); 
// Speed up dequeue by getting out quickly if this is just a lookup 
if ( !data ) { 
return q || []; 
} 
if ( !q || jQuery.isArray(data) ) { 
q = jQuery.data( elem, type, jQuery.makeArray(data) ); 
} else { 
q.push( data ); 
} 
return q; 
}, 
dequeue: function( elem, type ) { 
type = type || "fx"; 
var queue = jQuery.queue( elem, type ), fn = queue.shift(); 
// If the fx queue is dequeued, always remove the progress sentinel 
if ( fn === "inprogress" ) { 
fn = queue.shift(); 
} 
if ( fn ) { 
// Add a progress sentinel to prevent the fx queue from being 
// automatically dequeued 
if ( type === "fx" ) { 
queue.unshift("inprogress"); 
} 
fn.call(elem, function() { 
jQuery.dequeue(elem, type); 
}); 
} 
} 
}); 
jQuery.fn.extend({ 
queue: function( type, data ) { 
if ( typeof type !== "string" ) { 
data = type; 
type = "fx"; 
} 
if ( data === undefined ) { 
return jQuery.queue( this[0], type ); 
} 
return this.each(function( i, elem ) { 
var queue = jQuery.queue( this, type, data ); 
if ( type === "fx" && queue[0] !== "inprogress" ) { 
jQuery.dequeue( this, type ); 
} 
}); 
}, 
dequeue: function( type ) { 
return this.each(function() { 
jQuery.dequeue( this, type ); 
}); 
}, 
// Based off of the plugin by Clint Helfers, with permission. 
// http://blindsignals.com/index.php/2009/07/jquery-delay/ 
delay: function( time, type ) { 
time = jQuery.fx ? jQuery.fx.speeds[time] || time : time; 
type = type || "fx"; 
return this.queue( type, function() { 
var elem = this; 
setTimeout(function() { 
jQuery.dequeue( elem, type ); 
}, time ); 
}); 
}, 
clearQueue: function( type ) { 
return this.queue( type || "fx", [] ); 
} 
});

1.6添加了_mark,_unmark,promise。queue是让函数同属一个队伍里面,目的是让动画一个接一个执行。_mark则是让它们各自拥有队伍,并列执行(虽然它们只记录异步列队中已被执行的函数个数)。promise则在这些并发执行的动画执行后才执行另些一些回调(或动画)。
(function( jQuery ) { 
function handleQueueMarkDefer( elem, type, src ) { 
//清空记录deferred个数的字段,函数列队与异步列队 
var deferDataKey = type + "defer", 
queueDataKey = type + "queue", 
markDataKey = type + "mark", 
defer = jQuery.data( elem, deferDataKey, undefined, true ); 
if ( defer && 
( src === "queue" || !jQuery.data( elem, queueDataKey, undefined, true ) ) && 
( src === "mark" || !jQuery.data( elem, markDataKey, undefined, true ) ) ) { 
// Give room for hard-coded callbacks to fire first 
// and eventually mark/queue something else on the element 
setTimeout( function() { 
if ( !jQuery.data( elem, queueDataKey, undefined, true ) && 
!jQuery.data( elem, markDataKey, undefined, true ) ) { 
jQuery.removeData( elem, deferDataKey, true ); 
defer.resolve(); 
} 
}, 0 ); 
} 
} 
jQuery.extend({ 
_mark: function( elem, type ) { 
if ( elem ) { 
type = (type || "fx") + "mark";//创建一个以mark为后缀的字段,用于记录此列队中个数 
jQuery.data( elem, type, (jQuery.data(elem,type,undefined,true) || 0) + 1, true ); 
} 
}, 
_unmark: function( force, elem, type ) { 
if ( force !== true ) { 
type = elem; 
elem = force; 
force = false; 
} 
if ( elem ) { 
type = type || "fx"; 
var key = type + "mark", 
//让个数减1,如果第一个参数为true,就强逼减至0 
count = force ? 0 : ( (jQuery.data( elem, key, undefined, true) || 1 ) - 1 ); 
if ( count ) { 
jQuery.data( elem, key, count, true ); 
} else {//如果为0,就移除它 
jQuery.removeData( elem, key, true ); 
handleQueueMarkDefer( elem, type, "mark" ); 
} 
} 
}, 
queue: function( elem, type, data ) { 
if ( elem ) { 
type = (type || "fx") + "queue"; 
var q = jQuery.data( elem, type, undefined, true ); 
// Speed up dequeue by getting out quickly if this is just a lookup 
if ( data ) { 
if ( !q || jQuery.isArray(data) ) { 
q = jQuery.data( elem, type, jQuery.makeArray(data), true ); 
} else { 
q.push( data ); 
} 
} 
return q || []; 
} 
}, 
dequeue: function( elem, type ) { 
type = type || "fx"; 
var queue = jQuery.queue( elem, type ), 
fn = queue.shift(), 
defer; 
// If the fx queue is dequeued, always remove the progress sentinel 
if ( fn === "inprogress" ) { 
fn = queue.shift(); 
} 
if ( fn ) { 
// Add a progress sentinel to prevent the fx queue from being 
// automatically dequeued 
if ( type === "fx" ) { 
queue.unshift("inprogress"); 
} 
fn.call(elem, function() { 
jQuery.dequeue(elem, type); 
}); 
} 
if ( !queue.length ) { 
jQuery.removeData( elem, type + "queue", true ); 
handleQueueMarkDefer( elem, type, "queue" ); 
} 
} 
}); 
jQuery.fn.extend({ 
queue: function( type, data ) { 
if ( typeof type !== "string" ) { 
data = type; 
type = "fx"; 
} 
if ( data === undefined ) { 
return jQuery.queue( this[0], type ); 
} 
return this.each(function() { 
var queue = jQuery.queue( this, type, data ); 
if ( type === "fx" && queue[0] !== "inprogress" ) { 
jQuery.dequeue( this, type ); 
} 
}); 
}, 
dequeue: function( type ) { 
return this.each(function() { 
jQuery.dequeue( this, type ); 
}); 
}, 
// Based off of the plugin by Clint Helfers, with permission. 
// http://blindsignals.com/index.php/2009/07/jquery-delay/ 
delay: function( time, type ) { 
time = jQuery.fx ? jQuery.fx.speeds[time] || time : time; 
type = type || "fx"; 
return this.queue( type, function() { 
var elem = this; 
setTimeout(function() { 
jQuery.dequeue( elem, type ); 
}, time ); 
}); 
}, 
clearQueue: function( type ) { 
return this.queue( type || "fx", [] ); 
}, 
//把jQuery对象装进一个异步列队,允许它在一系列动画中再执行之后绑定的回调 
promise: function( type, object ) { 
if ( typeof type !== "string" ) { 
object = type; 
type = undefined; 
} 
type = type || "fx"; 
var defer = jQuery.Deferred(), 
elements = this, 
i = elements.length, 
count = 1, 
deferDataKey = type + "defer", 
queueDataKey = type + "queue", 
markDataKey = type + "mark"; 
function resolve() { 
if ( !( --count ) ) { 
defer.resolveWith( elements, [ elements ] ); 
} 
} 
while( i-- ) { 
//如果它之前已经使用过unmark, queue等方法,那么我们将生成一个新的Deferred放进缓存系统 
if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) || 
( jQuery.data( elements[ i ], queueDataKey, undefined, true ) || 
jQuery.data( elements[ i ], markDataKey, undefined, true ) ) && 
jQuery.data( elements[ i ], deferDataKey, jQuery._Deferred(), true ) )) { 
count++; 
tmp.done( resolve ); 
} 
} 
resolve(); 
return defer.promise(); 
} 
}); 
})( jQuery );

jQuery.ajax模块也被染指,$.XHR对象,当作HTTPXMLRequest对象的仿造器是由一个Deferred对象与一个_Deferred的对象构成。
deferred = jQuery.Deferred(), 
completeDeferred = jQuery._Deferred(), 
jqXHR ={/**/} 
//.... 
deferred.promise( jqXHR ); 
jqXHR.success = jqXHR.done; 
jqXHR.error = jqXHR.fail; 
jqXHR.complete = completeDeferred.done;

jQuery1.7,从deferred模块中分化出callback模块,其实就是之前的_Deferred的增强版,添加去重,锁定,return false时中断执行下一个回调,清空等功能。
(function( jQuery ) { 
// String to Object flags format cache 
var flagsCache = {}; 
// Convert String-formatted flags into Object-formatted ones and store in cache 
function createFlags( flags ) { 
var object = flagsCache[ flags ] = {}, 
i, length; 
flags = flags.split( /\s+/ ); 
for ( i = 0, length = flags.length; i < length; i++ ) { 
object[ flags[i] ] = true; 
} 
return object; 
} 
/* 
* Create a callback list using the following parameters: 
* 
* flags: an optional list of space-separated flags that will change how 
* the callback list behaves 
* 
* By default a callback list will act like an event callback list and can be 
* "fired" multiple times. 
* 
* Possible flags: 
* 
* once: will ensure the callback list can only be fired once (like a Deferred) 
* 
* memory: will keep track of previous values and will call any callback added 
* after the list has been fired right away with the latest "memorized" 
* values (like a Deferred) 
* 
* unique: will ensure a callback can only be added once (no duplicate in the list) 
* 
* stopOnFalse: interrupt callings when a callback returns false 
* 
*/ 
jQuery.Callbacks = function( flags ) { 
// Convert flags from String-formatted to Object-formatted 
// (we check in cache first) 
flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {}; 
var // Actual callback list 
list = [], 
// Stack of fire calls for repeatable lists 
stack = [], 
// Last fire value (for non-forgettable lists) 
memory, 
// Flag to know if list is currently firing 
firing, 
// First callback to fire (used internally by add and fireWith) 
firingStart, 
// End of the loop when firing 
firingLength, 
// Index of currently firing callback (modified by remove if needed) 
firingIndex, 
// Add one or several callbacks to the list 
add = function( args ) { 
var i, 
length, 
elem, 
type, 
actual; 
for ( i = 0, length = args.length; i < length; i++ ) { 
elem = args[ i ]; 
type = jQuery.type( elem ); 
if ( type === "array" ) { 
// Inspect recursively 
add( elem ); 
} else if ( type === "function" ) { 
// Add if not in unique mode and callback is not in 
if ( !flags.unique || !self.has( elem ) ) { 
list.push( elem ); 
} 
} 
} 
}, 
// Fire callbacks 
fire = function( context, args ) { 
args = args || []; 
memory = !flags.memory || [ context, args ]; 
firing = true; 
firingIndex = firingStart || 0; 
firingStart = 0; 
firingLength = list.length; 
for ( ; list && firingIndex < firingLength; firingIndex++ ) { 
if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) { 
memory = true; // Mark as halted 
break; 
} 
} 
firing = false; 
if ( list ) { 
if ( !flags.once ) { 
if ( stack && stack.length ) { 
memory = stack.shift(); 
self.fireWith( memory[ 0 ], memory[ 1 ] ); 
} 
} else if ( memory === true ) { 
self.disable(); 
} else { 
list = []; 
} 
} 
}, 
// Actual Callbacks object 
self = { 
// Add a callback or a collection of callbacks to the list 
add: function() { 
if ( list ) { 
var length = list.length; 
add( arguments ); 
// Do we need to add the callbacks to the 
// current firing batch? 
if ( firing ) { 
firingLength = list.length; 
// With memory, if we're not firing then 
// we should call right away, unless previous 
// firing was halted (stopOnFalse) 
} else if ( memory && memory !== true ) { 
firingStart = length; 
fire( memory[ 0 ], memory[ 1 ] ); 
} 
} 
return this; 
}, 
// Remove a callback from the list 
remove: function() { 
if ( list ) { 
var args = arguments, 
argIndex = 0, 
argLength = args.length; 
for ( ; argIndex < argLength ; argIndex++ ) { 
for ( var i = 0; i < list.length; i++ ) { 
if ( args[ argIndex ] === list[ i ] ) { 
// Handle firingIndex and firingLength 
if ( firing ) { 
if ( i <= firingLength ) { 
firingLength--; 
if ( i <= firingIndex ) { 
firingIndex--; 
} 
} 
} 
// Remove the element 
list.splice( i--, 1 ); 
// If we have some unicity property then 
// we only need to do this once 
if ( flags.unique ) { 
break; 
} 
} 
} 
} 
} 
return this; 
}, 
// Control if a given callback is in the list 
has: function( fn ) { 
if ( list ) { 
var i = 0, 
length = list.length; 
for ( ; i < length; i++ ) { 
if ( fn === list[ i ] ) { 
return true; 
} 
} 
} 
return false; 
}, 
// Remove all callbacks from the list 
empty: function() { 
list = []; 
return this; 
}, 
// Have the list do nothing anymore 
disable: function() { 
list = stack = memory = undefined; 
return this; 
}, 
// Is it disabled? 
disabled: function() { 
return !list; 
}, 
// Lock the list in its current state 
lock: function() { 
stack = undefined; 
if ( !memory || memory === true ) { 
self.disable(); 
} 
return this; 
}, 
// Is it locked? 
locked: function() { 
return !stack; 
}, 
// Call all callbacks with the given context and arguments 
fireWith: function( context, args ) { 
if ( stack ) { 
if ( firing ) { 
if ( !flags.once ) { 
stack.push( [ context, args ] ); 
} 
} else if ( !( flags.once && memory ) ) { 
fire( context, args ); 
} 
} 
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 !!memory; 
} 
}; 
return self; 
}; 
})( jQuery );

这期间有还个小插曲,jQuery团队还想增加一个叫Topic的模块,内置发布者订阅者机制,但这封装太溥了,结果被否决。
(function( jQuery ) { 
var topics = {}, 
sliceTopic = [].slice; 
jQuery.Topic = function( id ) { 
var callbacks, 
method, 
topic = id && topics[ id ]; 
if ( !topic ) { 
callbacks = jQuery.Callbacks(); 
topic = { 
publish: callbacks.fire, 
subscribe: callbacks.add, 
unsubscribe: callbacks.remove 
}; 
if ( id ) { 
topics[ id ] = topic; 
} 
} 
return topic; 
}; 
jQuery.extend({ 
subscribe: function( id ) { 
var topic = jQuery.Topic( id ), 
args = sliceTopic.call( arguments, 1 ); 
topic.subscribe.apply( topic, args ); 
return { 
topic: topic, 
args: args 
}; 
}, 
unsubscribe: function( id ) { 
var topic = id && id.topic || jQuery.Topic( id ); 
topic.unsubscribe.apply( topic, id && id.args || 
sliceTopic.call( arguments, 1 ) ); 
}, 
publish: function( id ) { 
var topic = jQuery.Topic( id ); 
topic.publish.apply( topic, sliceTopic.call( arguments, 1 ) ); 
} 
}); 
})( jQuery );

虽然把大量代码移动callbacks,但1.7的Deferred却一点没有没变小,它变得更重型,它由三个函数列队组成了。并且返回的是Promise对象,比原来多出了pipe, state, progress, always方法。ajax那边就变成这样:
deferred = jQuery.Deferred(), 
completeDeferred = jQuery.Callbacks( "once memory" ), 
deferred.promise( jqXHR ); 
jqXHR.success = jqXHR.done; 
jqXHR.error = jqXHR.fail; 
jqXHR.complete = completeDeferred.add;

queue那边也没变多少。
(function( jQuery ) { 
function handleQueueMarkDefer( elem, type, src ) { 
var deferDataKey = type + "defer", 
queueDataKey = type + "queue", 
markDataKey = type + "mark", 
defer = jQuery._data( elem, deferDataKey ); 
if ( defer && 
( src === "queue" || !jQuery._data(elem, queueDataKey) ) && 
( src === "mark" || !jQuery._data(elem, markDataKey) ) ) { 
// Give room for hard-coded callbacks to fire first 
// and eventually mark/queue something else on the element 
setTimeout( function() { 
if ( !jQuery._data( elem, queueDataKey ) && 
!jQuery._data( elem, markDataKey ) ) { 
jQuery.removeData( elem, deferDataKey, true ); 
defer.fire(); 
} 
}, 0 ); 
} 
} 
jQuery.extend({ 
_mark: function( elem, type ) { 
if ( elem ) { 
type = ( type || "fx" ) + "mark"; 
jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 ); 
} 
}, 
_unmark: function( force, elem, type ) { 
if ( force !== true ) { 
type = elem; 
elem = force; 
force = false; 
} 
if ( elem ) { 
type = type || "fx"; 
var key = type + "mark", 
count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 ); 
if ( count ) { 
jQuery._data( elem, key, count ); 
} else { 
jQuery.removeData( elem, key, true ); 
handleQueueMarkDefer( elem, type, "mark" ); 
} 
} 
}, 
queue: function( elem, type, data ) { 
var q; 
if ( elem ) { 
type = ( type || "fx" ) + "queue"; 
q = jQuery._data( elem, type ); 
// Speed up dequeue by getting out quickly if this is just a lookup 
if ( data ) { 
if ( !q || jQuery.isArray(data) ) { 
q = jQuery._data( elem, type, jQuery.makeArray(data) ); 
} else { 
q.push( data ); 
} 
} 
return q || []; 
} 
}, 
dequeue: function( elem, type ) { 
type = type || "fx"; 
var queue = jQuery.queue( elem, type ), 
fn = queue.shift(), 
hooks = {}; 
// If the fx queue is dequeued, always remove the progress sentinel 
if ( fn === "inprogress" ) { 
fn = queue.shift(); 
} 
if ( fn ) { 
// Add a progress sentinel to prevent the fx queue from being 
// automatically dequeued 
if ( type === "fx" ) { 
queue.unshift( "inprogress" ); 
} 
jQuery._data( elem, type + ".run", hooks ); 
fn.call( elem, function() { 
jQuery.dequeue( elem, type ); 
}, hooks ); 
} 
if ( !queue.length ) { 
jQuery.removeData( elem, type + "queue " + type + ".run", true ); 
handleQueueMarkDefer( elem, type, "queue" ); 
} 
} 
}); 
jQuery.fn.extend({ 
queue: function( type, data ) { 
var setter = 2; 
if ( typeof type !== "string" ) { 
data = type; 
type = "fx"; 
setter--; 
} 
if ( arguments.length < setter ) { 
return jQuery.queue( this[0], type ); 
} 
return data === undefined ? 
this : 
this.each(function() { 
var queue = jQuery.queue( this, type, data ); 
if ( type === "fx" && queue[0] !== "inprogress" ) { 
jQuery.dequeue( this, type ); 
} 
}); 
}, 
dequeue: function( type ) { 
return this.each(function() { 
jQuery.dequeue( this, type ); 
}); 
}, 
// Based off of the plugin by Clint Helfers, with permission. 
// http://blindsignals.com/index.php/2009/07/jquery-delay/ 
delay: function( time, type ) { 
time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; 
type = type || "fx"; 
return this.queue( type, function( next, hooks ) { 
var timeout = setTimeout( next, time ); 
hooks.stop = function() { 
clearTimeout( timeout ); 
}; 
}); 
}, 
clearQueue: function( type ) { 
return this.queue( type || "fx", [] ); 
}, 
// Get a promise resolved when queues of a certain type 
// are emptied (fx is the type by default) 
promise: function( type, object ) { 
if ( typeof type !== "string" ) { 
object = type; 
type = undefined; 
} 
type = type || "fx"; 
var defer = jQuery.Deferred(), 
elements = this, 
i = elements.length, 
count = 1, 
deferDataKey = type + "defer", 
queueDataKey = type + "queue", 
markDataKey = type + "mark", 
tmp; 
function resolve() { 
if ( !( --count ) ) { 
defer.resolveWith( elements, [ elements ] ); 
} 
} 
while( i-- ) { 
if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) || 
( jQuery.data( elements[ i ], queueDataKey, undefined, true ) || 
jQuery.data( elements[ i ], markDataKey, undefined, true ) ) && 
jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) { 
count++; 
tmp.add( resolve ); 
} 
} 
resolve(); 
return defer.promise( object ); 
} 
}); 
})( jQuery );

这时候,钩子机制其实已经在jQuery内部蔓延起来,1.5是css模块的cssHooks,1.6是属性模块的attrHooks, propHooks, boolHooks, nodeHooks,1.7是事件模块的fixHooks, keyHooks, mouseHooks,1.8是queue模块的_queueHooks,由于_queueHooks,queue终于瘦身了。
View Code?//1.8 
jQuery.extend({ 
queue: function( elem, type, data ) { 
var queue; 
if ( elem ) { 
type = ( type || "fx" ) + "queue"; 
queue = jQuery._data( elem, type ); 
// Speed up dequeue by getting out quickly if this is just a lookup 
if ( data ) { 
if ( !queue || jQuery.isArray(data) ) { 
queue = jQuery._data( elem, type, jQuery.makeArray(data) ); 
} else { 
queue.push( data ); 
} 
} 
return queue || []; 
} 
}, 
dequeue: function( elem, type ) { 
type = type || "fx"; 
var queue = jQuery.queue( elem, type ), 
fn = queue.shift(), 
hooks = jQuery._queueHooks( elem, type ), 
next = function() { 
jQuery.dequeue( elem, type ); 
}; 
// If the fx queue is dequeued, always remove the progress sentinel 
if ( fn === "inprogress" ) { 
fn = queue.shift(); 
} 
if ( fn ) { 
// Add a progress sentinel to prevent the fx queue from being 
// automatically dequeued 
if ( type === "fx" ) { 
queue.unshift( "inprogress" ); 
} 
// clear up the last queue stop function 
delete hooks.stop; 
fn.call( elem, next, hooks ); 
} 
if ( !queue.length && hooks ) { 
hooks.empty.fire(); 
} 
}, 
// not intended for public consumption - generates a queueHooks object, or returns the current one 
_queueHooks: function( elem, type ) { 
var key = type + "queueHooks"; 
return jQuery._data( elem, key ) || jQuery._data( elem, key, { 
empty: jQuery.Callbacks("once memory").add(function() { 
jQuery.removeData( elem, type + "queue", true ); 
jQuery.removeData( elem, key, true ); 
}) 
}); 
} 
}); 
jQuery.fn.extend({ 
queue: function( type, data ) { 
var setter = 2; 
if ( typeof type !== "string" ) { 
data = type; 
type = "fx"; 
setter--; 
} 
if ( arguments.length < setter ) { 
return jQuery.queue( this[0], type ); 
} 
return data === undefined ? 
this : 
this.each(function() { 
var queue = jQuery.queue( this, type, data ); 
// ensure a hooks for this queue 
jQuery._queueHooks( this, type ); 
if ( type === "fx" && queue[0] !== "inprogress" ) { 
jQuery.dequeue( this, type ); 
} 
}); 
}, 
dequeue: function( type ) { 
return this.each(function() { 
jQuery.dequeue( this, type ); 
}); 
}, 
// Based off of the plugin by Clint Helfers, with permission. 
// http://blindsignals.com/index.php/2009/07/jquery-delay/ 
delay: function( time, type ) { 
time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; 
type = type || "fx"; 
return this.queue( type, function( next, hooks ) { 
var timeout = setTimeout( next, time ); 
hooks.stop = function() { 
clearTimeout( timeout ); 
}; 
}); 
}, 
clearQueue: function( type ) { 
return this.queue( type || "fx", [] ); 
}, 
// Get a promise resolved when queues of a certain type 
// are emptied (fx is the type by default) 
promise: function( type, obj ) { 
var tmp, 
count = 1, 
defer = jQuery.Deferred(), 
elements = this, 
i = this.length, 
resolve = function() { 
if ( !( --count ) ) { 
defer.resolveWith( elements, [ elements ] ); 
} 
}; 
if ( typeof type !== "string" ) { 
obj = type; 
type = undefined; 
} 
type = type || "fx"; 
while( i-- ) { 
if ( (tmp = jQuery._data( elements[ i ], type + "queueHooks" )) && tmp.empty ) { 
count++; 
tmp.empty.add( resolve ); 
} 
} 
resolve(); 
return defer.promise( obj ); 
} 
});

同时,动画模块迎来了它第三次大重构,它也有一个钩子Tween.propHooks。它多出两个对象,其中Animation返回一个异步列队,Tween 是用于处理单个样式或属性的变化,相当于之前Fx对象。animate被抽空了,它在1.72可是近百行的规模。jQuery通过钩子机制与分化出一些新的对象,将一些巨型方法重构掉。现在非常长的方法只龟缩在节点模块,回调模块。
animate: function( prop, speed, easing, callback ) { 
var empty = jQuery.isEmptyObject( prop ), 
optall = jQuery.speed( speed, easing, callback ), 
doAnimation = function() { 
// Operate on a copy of prop so per-property easing won't be lost 
var anim = Animation( this, jQuery.extend( {}, prop ), optall ); 
// Empty animations resolve immediately 
if ( empty ) { 
anim.stop( true ); 
} 
}; 
return empty || optall.queue === false ? 
this.each( doAnimation ) : 
this.queue( optall.queue, doAnimation ); 
},

到目前为止,所有异步的东西都被jQuery改造成异步列队的“子类”或叫“变种”更合适些。如domReady, 动画,AJAX,与执行了promise或delay或各种特效方法之后的jQuery对象。于是所有异步的东西在promise的加护下,像同步那样编写异步程序。
Javascript 相关文章推荐
javascript 节点遍历函数
Mar 28 Javascript
jQuery实现在textarea指定位置插入字符或表情的方法
Mar 11 Javascript
javascript实现dom动态创建省市纵向列表菜单的方法
May 14 Javascript
浅谈javascript中的DOM方法
Jul 16 Javascript
javascript中的 object 和 function小结
Aug 14 Javascript
Javascript ES6中对象类型Sets的介绍与使用详解
Jul 17 Javascript
angular实现input输入监听的示例
Aug 31 Javascript
vue中组件的过渡动画及实现代码
Nov 21 Javascript
jQuery使用bind动态绑定事件无效的处理方法
Dec 11 jQuery
nuxt中使用路由守卫的方法步骤
Jan 27 Javascript
快速对接payjq的个人微信支付接口过程解析
Aug 15 Javascript
js实现简单贪吃蛇游戏
May 15 Javascript
Google的跟踪代码 动态加载js代码方法应用
Nov 12 #Javascript
Javascript开发之三数组对象实例介绍
Nov 12 #Javascript
在浏览器窗口上添加遮罩层的方法
Nov 12 #Javascript
php对mongodb的扩展(小试牛刀)
Nov 11 #Javascript
php对mongodb的扩展(初识如故)
Nov 11 #Javascript
JavaScript判断DOM何时加载完毕的技巧
Nov 11 #Javascript
JavaScript中的匀速运动和变速(缓冲)运动详细介绍
Nov 11 #Javascript
You might like
用PHP实现ODBC数据分页显示一例
2006/10/09 PHP
简单介绍win7下搭建apache+php+mysql开发环境
2015/08/06 PHP
手把手编写PHP框架 深入了解MVC运行流程
2016/09/19 PHP
php利用imagemagick实现复古老照片效果实例
2017/02/16 PHP
基于thinkPHP3.2实现微信接入及查询token值的方法
2017/04/18 PHP
基于JQuery的类似新浪微博展示信息效果的代码
2012/07/23 Javascript
文字不间断滚动(上下左右)实例代码
2013/04/21 Javascript
利用NodeJS的子进程(child_process)调用系统命令的方法分享
2013/06/05 NodeJs
js图片向右一张张滚动效果实例代码
2013/11/23 Javascript
jQuery中:enabled选择器用法实例
2015/01/04 Javascript
JavaScript中定义函数的三种方法
2015/03/12 Javascript
javascript实现将文件保存到本地方法汇总
2015/07/26 Javascript
深入浅析JavaScript中的scrollTop
2016/07/11 Javascript
微信小程序之小豆瓣图书实例
2016/11/30 Javascript
vue-router 中router-view不能渲染的解决方法
2017/05/23 Javascript
基于Vue过渡状态实例讲解
2017/09/14 Javascript
微信小程序渲染性能调优小结
2019/07/30 Javascript
微信小程序实现星级评价
2019/11/20 Javascript
解决vuex改变了state的值,但是页面没有更新的问题
2020/11/12 Javascript
nestjs中异常过滤器Exceptionfilter的具体使用
2021/02/07 Javascript
Linux 发邮件磁盘空间监控(python)
2016/04/23 Python
Python使用Paramiko模块编写脚本进行远程服务器操作
2016/05/05 Python
python学习之面向对象【入门初级篇】
2017/01/21 Python
python 实现提取某个索引中某个时间段的数据方法
2019/02/01 Python
Python Image模块基本图像处理操作小结
2019/04/13 Python
Python中生成一个指定长度的随机字符串实现示例
2019/11/06 Python
Jupyter Notebook 实现正常显示中文和负号
2020/04/24 Python
马克华菲官方商城:Mark Fairwhale
2016/09/04 全球购物
俄罗斯Sportmarket体育在线商店:用于旅游和户外活动
2019/11/12 全球购物
药品促销活动方案
2014/02/14 职场文书
2014年毕业演讲稿范文
2014/05/13 职场文书
会议欢迎词范文
2015/01/27 职场文书
通讯稿范文
2015/07/22 职场文书
2015大学党建带团建工作总结
2015/07/23 职场文书
朋友聚会祝酒词
2015/08/10 职场文书
浅谈Python数学建模之固定费用问题
2021/06/23 Python