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 相关文章推荐
jquery实现智能感知连接外网搜索
May 21 Javascript
jquery中的事件处理详细介绍
Jun 24 Javascript
Extjs4 Treegrid 使用心得分享(经验篇)
Jul 01 Javascript
浅析JQuery UI Dialog的样式设置问题
Dec 18 Javascript
jQuery实现倒计时按钮功能代码分享
Sep 03 Javascript
jQuery实现瀑布流布局
Dec 12 Javascript
JavaScript动态提示输入框输入字数的方法
Jul 27 Javascript
javascript如何实现暂停功能
Nov 06 Javascript
将鼠标焦点定位到文本框最后(代码分享)
Jan 11 Javascript
vue中用动态组件实现选项卡切换效果
Mar 25 Javascript
Angular之toDoList的实现代码示例
Dec 02 Javascript
JavaScript遍历数组和对象的元素简单操作示例
Jul 09 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实现HTTP断点续传的方法
2015/06/17 PHP
Yii2框架引用bootstrap中日期插件yii2-date-picker的方法
2016/01/09 PHP
修改jquery里的dialog对话框插件为框架页(iframe) 的方法
2010/09/14 Javascript
jquery插件制作 自增长输入框实现代码
2012/08/17 jQuery
window.open不被拦截的实现代码
2012/08/22 Javascript
js实现拖拽 闭包函数详细介绍
2012/11/25 Javascript
javascript为下拉列表动态添加数据项
2014/05/23 Javascript
checkbox选中与未选中判断示例
2014/08/04 Javascript
PhotoShop给图片自动添加边框及EXIF信息的JS脚本
2015/02/15 Javascript
JQuery给select添加/删除节点的实现代码
2016/04/26 Javascript
Angularjs手动解析表达式($parse)
2016/10/12 Javascript
jQuery实现6位数字密码输入框
2016/12/29 Javascript
Node.js 中exports 和 module.exports 的区别
2017/03/14 Javascript
Vue 项目部署到服务器的问题解决方法
2017/12/05 Javascript
vue之父子组件间通信实例讲解(props、$ref、$emit)
2018/05/22 Javascript
详解Vue SPA项目优化小记
2018/07/03 Javascript
layui实现数据表格自定义数据项
2019/10/26 Javascript
Pandas 数据框增、删、改、查、去重、抽样基本操作方法
2018/04/12 Python
Python实现的统计文章单词次数功能示例
2019/07/08 Python
简单了解Django ContentType内置组件
2019/07/23 Python
pyx文件 生成pyd 文件用于 cython调用的实现
2021/03/04 Python
什么是三层交换,说说和路由的区别在那里
2014/09/01 面试题
爽歪歪广告词
2014/03/20 职场文书
学习雷锋倡议书
2014/04/15 职场文书
导师推荐信范文
2014/05/09 职场文书
园林系毕业生求职信
2014/06/23 职场文书
药品营销专业毕业生自荐信
2014/07/02 职场文书
党的群众路线教育实践活动个人整改措施范文
2014/11/04 职场文书
应聘教师自荐信
2015/03/26 职场文书
2015年学校德育工作总结
2015/04/22 职场文书
2015年学校教研室主任工作总结
2015/07/20 职场文书
2016党员干部廉洁自律心得体会
2016/01/13 职场文书
简述python四种分词工具,盘点哪个更好用?
2021/04/13 Python
MySql新手入门的基本操作汇总
2021/05/13 MySQL
springboot @ConfigurationProperties和@PropertySource的区别
2021/06/11 Java/Android
Win11黑色桌面背景怎么办?Win11黑色壁纸解决方法汇总
2022/04/05 数码科技