六、队列Queue
队列Queue
队列时常用的数据结构之一,是一种特殊的线性表,只允许在表的前端(队头)进行删除操作(出队),在表的后端(队尾)进行插入操作(入队)。队列的特点是先进先出(FIFO, firstinfirstout),即最先插入的元素最先被删除。
jQuery的队列模块是队列的JavaScript实现,它提供了方法jQuery.queue()和.queue()实现入队操作,提供了方法jQuery.dequeue()和.dequeue()实现出队操作。不同于队列定义的是,jQuery的队列模块不仅支持函数的入队和出队操作,并且出队的函数会被自动调用。
jQuery.queue(elem, type, data) 返回或修改匹配元素关联的函数队列
jQuery.dequeue(elem, type) 出队并执行匹配元素关联的函数队列中的下一个函数
.queue(type, data) 返回第一个匹配元素关联的函数队列,或修改所有匹配元素关联的函数队列
.dequeue(type) 出队并执行匹配元素关联的函数队列中的下一个函数
.delay(time, type) 设置一个定时器,使得匹配元素关联的函数队列中后续的函数延迟出队和执行
.clearQueue(type) 移除匹配元素关联的函数队列中所有未被执行的函数
.promise(type, object) 返回一个异步队列的只读副本,观察每个匹配元素关联的某个类型的函数队列和计数器是否完成
在jQuery内部,队列模块为动画模块提供基础功能,负责存储动画函数、自动出队并执行动画函数,同时还要确保动画函数的顺序执行。

jQuery.extend({ queue: function (elem, type, data) { // 参数elem: DOM元素或JavaScript对象,在其上查找或修改队列 // 参数type: 字符串,表示队列名称,默认为标准动画fx // 参数data: 可选的函数或函数数组,不传时返回队列,其为函数时入队,为函数数组时替换队列 // 方法jQuery.queue(elem, type, data)用于返回或修改匹配元素关联的函数队列。共有3种用法。 /* jQuery.queue(element,[queueName]) Element,String // 返回匹配元素关联的函数队列 element:检查附加列队的DOM元素 queueName:字符串值,包含序列的名称。默认是 fx, 标准的效果序列。 jQuery.queue(element,queueName,newQueue) Element,String,Array // 修改匹配元素关联的函数队列,用函数数组newQueue替换当前队列 element:检查附加列队的DOM元素 queueName:字符串值,包含序列的名称。默认是 fx, 标准的效果序列。 newQueue:替换当前函数列队内容的数组 jQuery.queue(element,queueName,callback()) Element,String // 修改匹配元素关联的函数队列,添加函数callback()到队列中。 element:检查附加列队的DOM元素 queueName:字符串值,包含序列的名称。默认是 fx, 标准的效果序列。 callback():要添加进队列的函数 注意:jQuery.queue(elem, type, data)是一个低级方法,应该用.queue(type, data)代替。当使用jQuery.queue()添加一个函数时,应该确保jQuery.dequeue()最后被调用,以使下一个函数得以顺序执行。 */ var queue; if (elem) { type = (type || "fx") + "queue"; // 修正参数type,默认为动画队列“fx”。在参数type后加上后缀“queue”,表示这是一个队列 queue = dataPriv.get(elem, type); // 取出参数type对应的队列 // Speed up dequeue by getting out quickly if this is just a lookup if (data) { // 如果传入参数data,则入队或替换队列 if (!queue || Array.isArray(data)) { // 如果队列不存在或参数data是数组,则调用方法jQuery.makeArray(data)把参数data转换为数组,并替换队列 queue = dataPriv.access(elem, type, jQuery.makeArray(data)); } else { // 否则调用数组方法push()把参数data入队 queue.push(data); } } return queue || []; // 返回参数type对应的队列,如果队列不存在则返回空数组[] } }, dequeue: function (elem, type) { // 参数elem:DOM元素或JavaScript对象,在其上出队并执行函数 // 参数type: 字符串,表示队列名称,默认为动画队列fx // 方法jQuery.dequeue(elem, type)用于出队并执行匹配元素关联的函数队列中的下一个函数。 /* 方法jQuery.dequeue(elem, type)是一个低级方法,应该用.dequeue(type)代替。出队的函数应该直接或间接地再次调用方法jquery.dequeue(),以使下一个函数得以顺序执行。 */ type = type || "fx"; var queue = jQuery.queue(elem, type), // 取出参数type对应的队列 startLength = queue.length, fn = queue.shift(), // 调用数组方法shift()出队第一个函数 hooks = jQuery._queueHooks(elem, type), // 变量hooks用于存放出队的函数在执行时的数据 next = function () { jQuery.dequeue(elem, type); }; // If the fx queue is dequeued, always remove the progress sentinel if (fn === "inprogress") { // 修正动画函数的占位符。如果出队的是占位符“inprogress”,则丢弃再出队一个。只有动画队列会设置占位符“inprogress” fn = queue.shift(); startLength--; } if (fn) { // 设置动画占位符“inprogress”,表示动画函数正在执行中 // Add a progress sentinel to prevent the fx queue from being // automatically dequeued if (type === "fx") { // 动画队列会在队头放置一个占位符“inprogress”,表示动画函数正在执行 queue.unshift("inprogress"); } // Clear up the last queue stop function // 清除最后一个队列停止函数 delete hooks.stop; fn.call(elem, next, hooks); // 调用函数方法call()执行出队的函数,参数格式位fn.call(elem,next,hooks)。第一个参数elem指定了函数执行时的上下文,即关键字this指向的对象;第二个参数next是封装了jQuery.dequeue(elem, type)的函数,不会自动执行,需要在出队的函数返回前手动调用next(),以使下一个函数得以顺序执行;第三个参数hooks是一个空对象,出队的函数在执行过程中可以把数据存储在对象hooks上,在下一个函数出队前,可以调用jquery._data(elem,styp+'.run')取出上一个函数运行时的数据 } if (!startLength && hooks) { // 如果参数type对应的队列在出队后成为空队列,即所有函数都已出队并执行 hooks.empty.fire(); } }, // Not public - generate a queueHooks object, or return the current one _queueHooks: function (elem, type) { var key = type + "queueHooks"; return dataPriv.get(elem, key) || dataPriv.access(elem, key, { empty: jQuery.Callbacks("once memory").add(function () { dataPriv.remove(elem, [type + "queue", key]); }) }); } }); jQuery.fn.extend({ // 方法.queue(type, data)用于返回第一个匹配元素关联的函数队列,或修改所有匹配元素关联的函数队列。 /* .queue(queueName) // 返回第一个匹配元素关联的函数队列 .queue(queueName, newQueue) // 修改匹配元素关联的函数队列,用函数数组newQueue替换当前队列 .queue(queueName, callback(next)) // 修改匹配元素关联的函数队列,添加函数callback到队列中 每个DOM元素和JavaScript对象都可以通过jQuery绑定多个队列,但是在大多数程序中,只有一个队列(动画队列fx)被使用。队列允许异步地调用一系列行为,而不会停止程序执行。 // 当使用.queue()添加一个函数时,应该确保.dequeue()最后会被调用,以使下一个函数得以顺序执行。 // 方法.queue(type, data)通过调用方法jQuery.queue(elem, type, data)返回或修改匹配元素关联的函数队列。 */ queue: function (type, data) { // 参数type: 字符串,表示队列名称,默认是动画队列fx // 参数data: 可选的函数或函数数组。如果不传入该参数,则会返回当前队列;如果该参数为函数,则会被入队;如果该参数为函数数组,则用它替换当前队列。 var setter = 2; if (typeof type !== "string") { // 当参数格式为.queue(data)或.queue()时,修正参数data和type。 data = type; type = "fx"; setter--; } if (arguments.length < setter) { return jQuery.queue(this[0], type); } // 如果参数data是undefined,即没有传入参数data,则认为是取队列,返回this。 // 如果传入了函数data,则在每个匹配的元素上调用方法jQuery.queue(elem, type, data),把参数data(函数)入队,或者用参数data(函数数组)替换当前队列。对于动画队列fx,在动画函数入队后,如果没有动画函数正在执行,则立即出队并执行动画函数。最后返回当前jQuery对象。 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) { // 方法.dequeue(type)用于出队并执行匹配元素关联的函数队列中的下一个函数。 // 注意:出队的函数应该再次直接或间接地调用方法.dequeue(),以使下一个函数得以顺序执行。 // 方法.dequeue(type)通过调用方法jQuery.dequeue(elem, type)出队并执行下一个函数。 // 定义方法.dequeue(type),接受一个可选的字符串参数,表示队列名称,默认是动画队列fx。 // 在每个匹配的元素上调用方法jQuery.dequeue(elem, type),使参数type对应的函数队列中的下一个函数出队并执行。最后返回当前jQuery对象。 return this.each(function () { jQuery.dequeue(this, type); }); }, clearQueue: function (type) { // 方法.clearQueue(type)用于移除匹配元素关联的函数队列中的所有未被执行的函数。该方法通过调用.queue(type, data)实现,调用时传入一个空数组来替换当前函数队列。 return this.queue(type || "fx", []); }, // Get a promise resolved when queues of a certain type // are emptied (fx is the type by default) // 方法.promise(type, object)返回一个异步队列的只读副本,观察每个匹配元素关联的某个类型的队列和计数器是否完成。 // 当每个匹配元素关联的函数队列(type+"queue")变为空队列,关联的计数器(type+"mark")变为0时,则触发异步队列的成功回调函数,上下文和参数是调用方法.promise()集合(即jQuery对象);如果队列和计数器不存在,则立即触发成功回调函数。 // 参数type指定了对了和计数器的类型(即名称),默认为标准动画fx。以标准动画fx为例,当匹配元素的所有阻塞动画(基于队列实现)和非阻塞动画(基于计数器实现)都完成后,触发异步队列的成功回调函数。 // 参数target是可选的对象。如果提供了参数target,方法.promise()会将只读副本的方法附加到该参数中,并返回。通过这种方式,可以为一个已存在的对象附加异步队列的行为。 // 注意:方法.promise(type, object)返回的异步队列只读副本连接了一个存储在DOM元素关联的数据缓存对象中的异步队列。因为方法.remove()不但会移除DOM元素,而且会移除关联的数据,所以它会阻止所有未触发的异步队列只读副本被触发。如果需要在异步队列只读副本触发前把DOM元素从文档树中移除,可使用方法.detach()代替,触发后再调用.removeData()移除关联的数据。 /* 实现原理 方法.promise(type, object)会创建一个异步队列来存储成功回调函数,并且会创建一个计数器来记录匹配元素关联的队列和计数器的状态,计数器的初始值为需要观察的元素的个数,同时它还会创建一个会使计数器减1的特殊回调函数,并把该特殊回调函数添加到需要观察的元素所关联的回调函数列表中。 当某个元素关联的函数队列成为空队列,关联的计数器变为0时,触发关联的回调函数列表,这时,特殊回调函数会被执行,方法.promise(type, object)的计数器减1。 当方法.promise(type, object)的计数器变为0,表示所有需要观察的元素关联的函数队列和计数器都已经完成,这时将会触发异步队列的成功回调函数。 */ promise: function (type, obj) { // 参数type:字符串,表示需要观察的队列名称和计数器名称,默认为标准动画fx // 参数obj:可选的对象,指向异步队列的只读方法附加的对象 var tmp, // tmp,while循环中的临时变量,指向DOM元素关联的回调函数列表 count = 1, // count,计数器,当后面的while循环结束时,其值变为需要观察的DOM元素的个数 defer = jQuery.Deferred(), // defer,异步队列,用于存放成功回调函数 elements = this, // elemnts,当前jQuery对象,即匹配的元素集合 i = this.length, // i,当前jQuery对象中DOM元素的个数,用于后面的while循环计数器 resolve = function () { // 回调函数resolve(),如果没有需要观察的元素,则立即触发异步队列的成功回调函数。 if (!(--count)) { defer.resolveWith(elements, [elements]); } }; if (typeof type !== "string") { obj = type; type = undefined; } type = type || "fx"; while (i--) { tmp = dataPriv.get(elements[i], type + "queueHooks"); if (tmp && tmp.empty) { count++; tmp.empty.add(resolve); } } resolve(); // 调用特殊回调函数resolve(),如果没有需要观察的元素,则立即触发异步队列的成功回调函数。 return defer.promise(obj); // 返回异步队列的只读副本。 } }); // Based off of the plugin by Clint Helfers, with permission. // 基于Clint Helfers的插件,获得许可。 // https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ // 方法.delay(time, type)用于设置一个定时器,以使匹配元素关联的函数队列中后续的函数延迟出队和执行。延迟时间的单位是毫秒,其值可以是数值,也可以是字符串“fast”或“slow”,分别表示200毫秒和600毫秒。 // 方法.delay(time, type)通过调用方法.queue(type, data)向关联的函数队列中插入一个新函数,在新函数内通过setTimeout()延迟下一个函数的出队时间。 jQuery.fn.delay = function( time, type ) { // 参数time:表示延迟时间,单位是毫秒,其值可以是数值,也可以是字符串“fast”或“slow”,分别表示200毫秒和600毫秒。 // 参数type:字符串,表示队列名称,默认为动画队列fx。 // 修正参数time,type。修正参数time时,会先尝试从jQuery.fx.speeds中读取该参数对应的时间,如果未取到则采用原始值。 time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; type = type || "fx"; // 在每个匹配元素上通过调用方法.queue(type, data)向关联的函数队列中插入一个新函数,在该函数内通过setTimeout延迟下一个函数的出队时间。 return this.queue( type, function( next, hooks ) { var timeout = window.setTimeout( next, time ); hooks.stop = function() { // 方法hooks.stop()用于取消定时器。在等待期间,其他代码可以调用jQuery._data(elem, type+".run")取出存储的对象hooks,然后调用其上的方法hooks.stop()取消定时器,停止函数出队。想要队列继续向前运行,再次调用.dequeue()即可。 window.clearTimeout( timeout ); }; } ); };