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 相关文章推荐
Ext.get() 和 Ext.query()组合使用实现最灵活的取元素方式
Sep 26 Javascript
jQuery DOM操作实例
Mar 05 Javascript
jquery.validate.js插件使用经验记录
Jul 02 Javascript
dreamweaver 8实现Jquery自动提示
Dec 04 Javascript
js实现用户注册协议倒计时的方法
Jan 21 Javascript
深入学习JavaScript的AngularJS框架中指令的使用方法
Mar 05 Javascript
JavaScript高仿支付宝倒计时页面及代码实现
Oct 21 Javascript
微信小程序 navbar实例详解
May 11 Javascript
Vue-router路由判断页面未登录跳转到登录页面的实例
Oct 26 Javascript
vue组件生命周期详解
Nov 07 Javascript
Vue的transition-group与Virtual Dom Diff算法的使用
Dec 09 Javascript
Bootstrap实现前端登录页面带验证码功能完整示例
Mar 26 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读取txt文件的内容并赋值给数组的代码
2011/11/03 PHP
php检查日期函数checkdate用法实例
2015/03/19 PHP
php实现中文转数字
2016/02/18 PHP
php中get_magic_quotes_gpc()函数说明
2017/02/06 PHP
php实现微信扫码支付
2017/03/26 PHP
PHP封装的page分页类定义与用法完整示例
2018/12/24 PHP
JavaScript开发规范要求(规范化代码)
2010/08/16 Javascript
onsubmit阻止form表单提交与onclick的相关操作
2010/09/03 Javascript
json的前台操作和后台操作实现代码
2012/01/20 Javascript
js处理表格对table进行修饰
2014/05/26 Javascript
JavaScript严格模式禁用With语句的原因
2014/10/20 Javascript
Jquery实现图片预加载与延时加载的方法
2014/12/22 Javascript
JQuery中extend的用法实例分析
2015/02/08 Javascript
GitHub上一些实用的JavaScript的文件压缩解压缩库推荐
2016/03/13 Javascript
Javascript类型系统之undefined和null浅析
2016/07/13 Javascript
Bootstrap Table使用方法详解
2016/08/01 Javascript
详细解读Jquery各Ajax函数($.get(),$.post(),$.ajax(),$.getJSON())
2016/08/15 Javascript
Javascript获取图片原始宽度和高度的方法详解
2016/09/20 Javascript
jQuery.cookie.js实现记录最近浏览过的商品功能示例
2017/01/23 Javascript
详解Vue路由开启keep-alive时的注意点
2017/06/20 Javascript
AngularJS实时获取并显示密码的方法
2018/02/06 Javascript
JS实现的视频弹幕效果示例
2018/08/17 Javascript
vue实现一个6个输入框的验证码输入组件功能的实例代码
2020/06/29 Javascript
JSON获取属性值方法代码实例
2020/06/30 Javascript
浅谈Python中用datetime包进行对时间的一些操作
2016/06/23 Python
Python实现SSH远程登陆,并执行命令的方法(分享)
2017/05/08 Python
Python 自动刷博客浏览量实例代码
2017/06/14 Python
python使用递归的方式建立二叉树
2019/07/03 Python
专门经营化妆刷的美国彩妆品牌:Sigma Beauty
2017/09/11 全球购物
加拿大床上用品、家居装饰、厨房和浴室产品购物网站:Linen Chest
2018/06/05 全球购物
什么是TCP/IP
2014/07/27 面试题
领导班子对照检查材料
2014/09/22 职场文书
2014小学二年级班主任工作总结
2014/12/05 职场文书
碧霞祠导游词
2015/02/09 职场文书
小学教师教学随笔
2015/08/14 职场文书
python使用tkinter实现透明窗体上绘制随机出现的小球(实例代码)
2021/05/17 Python