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 相关文章推荐
IE6下js通过css隐藏select的一个bug
Aug 16 Javascript
JavaScript中将一个值转换为字符串的方法分析[译]
Sep 21 Javascript
禁用Enter键表单自动提交实现代码
May 22 Javascript
基于jQuery实现的菜单切换效果
Oct 16 Javascript
node.js中实现kindEditor图片上传功能的方法教程
Apr 26 Javascript
微信小程序 request接口的封装实例代码
Apr 26 Javascript
微信小程序 自定义Toast实例代码
Jun 12 Javascript
浅谈Vuex@2.3.0 中的 state 支持函数申明
Nov 22 Javascript
微信小程序使用input组件实现密码框功能【附源码下载】
Dec 11 Javascript
jQuery实现常见的隐藏与展示列表效果示例
Jun 04 jQuery
详解vue 兼容IE报错解决方案
Dec 29 Javascript
手把手教您实现react异步加载高阶组件
Apr 07 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
YII Framework框架教程之使用YIIC快速创建YII应用详解
2016/03/15 PHP
Laravel 自定命令以及生成文件的例子
2019/10/23 PHP
Laravel框架下载,安装及路由操作图文详解
2019/12/04 PHP
[原创]站长必须要知道的javascript广告代码
2007/05/30 Javascript
Prototype源码浅析 String部分(一)之有关indexOf优化
2012/01/15 Javascript
ANGULARJS中使用JQUERY分页控件
2015/09/16 Javascript
jQuery点击改变class并toggle及toggleClass()方法定义用法
2015/12/11 Javascript
jQuery 获取多选框的值及多选框中文的函数
2016/05/16 Javascript
js阻止冒泡和默认事件(默认行为)详解
2016/10/20 Javascript
canvas绘制七巧板
2017/02/03 Javascript
jquery获取下拉框中的循环值
2017/02/08 Javascript
基于 webpack2 实现的多入口项目脚手架详解
2017/06/26 Javascript
详细分析单线程JS执行问题
2017/11/22 Javascript
vue通过路由实现页面刷新的方法
2018/01/25 Javascript
微信小程序实现收藏与取消收藏切换图片功能
2018/08/03 Javascript
JavaScript使用递归和循环实现阶乘的实例代码
2018/08/28 Javascript
Vue实现的父组件向子组件传值功能示例
2019/01/19 Javascript
mpvue性能优化实战技巧(小结)
2019/04/17 Javascript
vue-router路由模式详解(小结)
2019/08/26 Javascript
解决layui的table插件无法多层级获取json数据的问题
2019/09/19 Javascript
微信小程序使用 vant Dialog组件的正确方式
2020/02/21 Javascript
从Node.js事件触发器到Vue自定义事件的深入讲解
2020/06/26 Javascript
详解React的回调渲染模式
2020/09/10 Javascript
微信小程序实现翻牌抽奖动画
2020/09/21 Javascript
html中创建并调用vue组件的几种方法汇总
2020/11/17 Javascript
[00:37]食人魔魔法师轮盘吉兆顺应全新至宝将拥有额外款式
2019/12/19 DOTA
一个超级简单的python web程序
2014/09/11 Python
在Python的Django框架上部署ORM库的教程
2015/04/20 Python
django批量导入xml数据
2016/10/16 Python
Python requests发送post请求的一些疑点
2018/05/20 Python
对python使用telnet实现弱密码登录的方法详解
2019/01/26 Python
详解Flask前后端分离项目案例
2020/07/24 Python
中等生评语大全
2014/05/04 职场文书
校车安全责任书
2014/08/25 职场文书
2016年劳模先进事迹材料
2016/02/25 职场文书
基于Golang 高并发问题的解决方案
2021/05/08 Golang