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

  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的加护下,像同步那样编写异步程序。