前言:queue()
方法和 dequeue()
方法是为 jQuery 的动画服务的,目的是为了允许一系列动画函数被异步调用,但不会阻塞程序。
所以这篇是为 jQuery 的动画解析做准备的。
一、$
.queue()、$
.dequeue() 和 $()
.queue()、$()
.dequeue() 的区别
(1)$().queue()
和$().dequeue()
这俩是 jQuery.fn.extend()
中的方法,也就是供开发者使用的方法,其内部会分别调用 $.queue()
和 $.dequeue()
方法。
// 源码 4686 行
jQuery.fn.extend( {queue: function( type, data) {
xxx
return jQuery.queue(this[ 0], type )
},
dequeue: function(type, data) {return jQuery.dequeue( this, type)
},
})
(2)$.queue()
和 $.dequeue()
这俩是 jQuery.extend()
中的方法,也就是 jQuery 内部使用的方法。
// 源码 4594 行
jQuery.extend( {queue: function( elem, type, data) {},
dequeue: function(elem, type) {},})
二、$
().queue()
作用 1:
作为 setter
,将function(){}
存进特定队列中。
<div id="A" style="background-color: deeppink"> 这是 A </div>
<script>
function a() {console.log('a','a34')
}
function b() {console.log('b','b37')
}
// 将 a、b 方法存在类型为 type 的队列里
//jQuery.fn.queue 给 jQuery 对象 $("A")
/*setter*/
$("#A").queue('type', a)
$("#A").queue('type', b)
</script>
作用 2:
作为 getter
,取出特定队列中function(){}
的数组。
/*getter*/
$("#A").queue('type') //[a,b]
源码:
jQuery.fn.extend( {
// 入队
// 源码 4663 行
//'type', function(){xxx}
queue: function(type, data) {
var setter = 2;
if (typeof type !== "string") {
data = type;
type = "fx";
setter--;
}
// 如果参数小于 setter,就执行 jQuery.queue()方法
/* 这边就是 getter*/
if (arguments.length < setter) {//this[0] 目标 DOM 元素
//type "type"
// 返回[function a(){xxx},function b(){xxx}]
return jQuery.queue(this[ 0], type );
}
// 如果 data 是 undefined 就返回 jQuery 对象
return data === undefined ?
this :
this.each(function() {
/* 这边是 setter*/
var queue = jQuery.queue(this, type, data);
// Ensure a hooks for this queue
// 确保该队列有一个 hooks
// 返回{empty:{
// 里面是 jQuery.Callbacks 方法
// 其中 add 方法被改写
// }}
jQuery._queueHooks(this, type);
/* 专门为 fx 动画做处理 */
if (type === "fx" && queue[ 0] !== "inprogress" ) {jQuery.dequeue( this, type);
}
} );
},
})
解析:
不涉及 fx 动画的话,本质是调用的内部的 jQuery.queue()
方法
(1)如果不足两个参数的话,就调用jQuery. queue()
来get
获取数据。
(2)如果大于等于两个参数的话,就调用 jQuery. queue()
来set
存数据,并且调用 jQuery._queueHooks()
,用来生成一个queueHooks
对象或者返回当前值。
(3)如果是 fx 动画,并且队头没有 inprogress
锁的话,就执行 jQuery.dequeue()
方法。
三、jQuery._queueHooks()
作用:
如果目标元素的数据缓存(dataPriv
)已存在名称 type
+queueHooks
的Hooks
话,则直接返回该 Hooks,
否则返回有 empty
属性的 jQuery.Callback()
方法生成的对象:
其中的 fire()
方法用来清除队列。
源码:
// Not public - generate a queueHooks object, or return the current one
//jQuery 内部方法,生成一个 queueHooks 对象或者返回当前值
// 目标元素,"type"
// 源码 4676 行
_queueHooks: function(elem, type) {
//typequeueHooks
var key = type + "queueHooks";
// 如果 dataPriv 已存在名称 typequeueHooks 的 Hooks 话,则直接返回该 Hooks
// 否则返回有 empty 属性的 jQuery.Callback()方法生成的对象
return dataPriv.get(elem, key) || dataPriv.access( elem, key, {empty: jQuery.Callbacks( "once memory").add(function() {dataPriv.remove( elem, [ type + "queue", key] );
} )
} );
}
解析:jQuery.Callbacks()
方法会放到 $.dequeue
后讲解
四、jQuery.queue()
作用:
把 callback
依次存入目标元素的 queue
中,或者取出queue
。
源码:
jQuery.extend( {
// 作用:目标元素可执行的任务队列
// 源码 4596 行
//elem 目标元素
//$("#A"),"type",function(){xxx}
queue: function(elem, type, data) {
var queue;
if (elem) {
//typequeue
type = (type || "fx") + "queue";
// 从数据缓存中获取 typequeue 队列,如果没有则为 undefined
queue = dataPriv.get(elem, type);
// Speed up dequeue by getting out quickly if this is just a lookup
if (data) {
// 如果 queue 不存在,或者 data 是 Array 的话
// 就创建 queue,queue=[data1,data2,...]
if (!queue || Array.isArray( data) ) {queue = dataPriv.access( elem, type, jQuery.makeArray( data) );
}
//queue 存在的话,就把 data push 进去
else {queue.push( data);
}
}
//queue=[a,b]
return queue || [];}
},
})
解析:
(1)作为setter
function a() {console.log('a','a34')
}
$("#A").queue('type', a)
此时 data
存在,并且是第一次创建 type='type'
的queue
,所以使用 dataPriv.access(elem, type, jQuery.makeArray( data) )
来创建 queue
,并把function a
push 进queue
中。
(2)作为getter
$("#A").queue('type') //[a,b]
此时 data
不存在,直接从数据缓存中获取 queue
并返回。
注意:jQuery.queue()
始终返回 queue
数组,而 $().queue()
会返回 jQuery 对象或者是 queue
数组。
五、$().dequeue()
作用:
移出队头的函数并执行该callback
。
源码:
jQuery.fn.extend( {
// 出队
// 移出队头的函数并执行它
// 源码 4717 行
dequeue: function(type) {return this.each( function() {jQuery.dequeue( this, type);
} );
},
})
解析:
其实就是执行 $.dequeue()
函数。
六、jQuery.dequeue()
作用:
同五。
源码:
jQuery.extend( {
// 源码 4624 行
// 目标元素,'type'
dequeue: function(elem, type) {
//'type'
type = type || "fx";
//get,获取目标元素的队列
var queue = jQuery.queue(elem, type),
// 长度
startLength = queue.length,
// 去除对首元素,并返回该元素
fn = queue.shift(),
// 确保该队列有一个 hooks
hooks = jQuery._queueHooks(elem, type),
//next 相当于 dequeue 的触发器
next = function() {jQuery.dequeue( elem, type);
};
// If the fx queue is dequeued, always remove the progress sentinel
// 如果 fn='inprogress',说明是 fx 动画队列正在出队,就移除 inprogress
if (fn === "inprogress") {fn = queue.shift();
startLength--;
}
if (fn) {
// Add a progress sentinel to prevent the fx queue from being
// automatically dequeued
// 如果是 fx 动画队列的话,就添加 inprogress 标志,来防止自动出队执行
// 意思应该是等上一个动画执行完毕后,再执行下一个动画
if (type === "fx") {queue.unshift( "inprogress");
}
// Clear up the last queue stop function
// 删除 hooks 的 stop 属性方法
delete hooks.stop;
// 递归 dequeue 方法
fn.call(elem, next, hooks);
}
console.log(startLength,'startLength4669')
// 如果队列是一个空数组,并且 hooks 存在的话,清除该队列
if (!startLength && hooks) {console.log('aaaa','bbbb4671')
// 进行队列清理
hooks.empty.fire();}
},
})
解析:
(1)inprogress
应该是一个锁,当 fx 动画执行动画 A 的时候,就加锁,当动画 A 执行完毕后,就解锁,再去运行下一个动画。
(2)注意下 fn.call(elem, next, hooks)
,保持fn
的this
是 element
的同时,给 fn
传的两个参数,分别为 next
和hooks
,方便操作。
(3)当 queue
是空数组的时候,就触发 hooks.empty
的fire()
方法,将 queue
清除。
七、jQuery.Callbacks()
作用:jQuery
的 callbacks
回调方法,返回一个 object,里面包含 a、b、c 方法,在执行任意一个方法后,这个方法依旧返回 a、b、c 方法,所以 jQuery.Callbacks()
是链式调用的关键函数。
在 _queueHooks
中有用到该函数:
dataPriv.access( elem, key, {empty: jQuery.Callbacks( "once memory").add(function() {dataPriv.remove( elem, [ type + "queue", key] );
} )
} );
源码:
/* 创建一个使用以下参数的 callback 列表
* Create a callback list using the following parameters:
* options: 是一个可选的空格分开的参数,它可以改变 callback 列表的行为或形成新的 option 对象
* options: an optional list of space-separated options that will change how
* the callback list behaves or a more traditional option object
* 默认情况下一个回调列表会表现成一个 event callback 列表并且会触发多次
* By default a callback list will act like an event callback list and can be
* "fired" multiple times.
* option 可选值:* Possible options:
* 确保 callback 列表只会被触发一次,比如 Deferred 对象
* once: will ensure the callback list can only be fired once (like a Deferred)
* 保持跟踪之前的 values,并且会在 list 用最新的 values 触发后,调用该回调函数
* 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)
* // 确保 callback 只会被添加一次
* unique: will ensure a callback can only be added once (no duplicate in the list)
* // 当 callbak 返回 false 时打断调用
* stopOnFalse: interrupt callings when a callback returns false
*
*/
// 源码 3407 行
//callbacks 回调对象,函数的统一管理
//once memory
jQuery.Callbacks = function(options) {
// Convert options from String-formatted to Object-formatted if needed
// (we check in cache first)
options = typeof options === "string" ?
//options: {once:true,memory:true}
createOptions(options) :
jQuery.extend({}, options );
// 用来知道 list 是否正被调用
var // Flag to know if list is currently firing
firing,
// Last fire value for non-forgettable lists
memory,
// Flag to know if list was already fired
fired,
// Flag to prevent firing
locked,
// Actual callback list
list = [],
// Queue of execution data for repeatable lists
queue = [],
// Index of currently firing callback (modified by add/remove as needed)
firingIndex = -1,
// 触发 list 中的回调函数
// Fire callbacks
fire = function() {
//true
// Enforce single-firing
//'once memory' 中的 'once' 只允许触发一次
locked = locked || options.once;
// Execute callbacks for all pending executions,
// respecting firingIndex overrides and runtime changes
fired = firing = true;
for (; queue.length; firingIndex = -1) {
// 从 queue 移除第一个元素,并返回该元素
memory = queue.shift();
while (++firingIndex < list.length) {
// Run callback and check for early termination
//memory=[document, Array(1)]
//memory[0]是 document
// 意思就是让 document 去执行 add()方法中添加的 callback 函数
if (list[ firingIndex].apply(memory[ 0], memory[1] ) === false &&
options.stopOnFalse ) {
// Jump to end and forget the data so .add doesn't re-fire
firingIndex = list.length;
memory = false;
}
}
}
// Forget the data if we're done with it
if (!options.memory) {memory = false;}
firing = false;
// Clean up if we're done firing for good
// 如果 once:true,清空 list 数组
if (locked) {
// Keep an empty list if we have data for future add calls
if (memory) {list = [];
// Otherwise, this object is spent
} else {list = "";}
}
},
// Actual Callbacks object
self = {
// 添加一个回调函数或者是一个回调函数的集合
// Add a callback or a collection of callbacks to the list
add: function() {if ( list) {
// If we have memory from a past run, we should fire after adding
if (memory && !firing) {
firingIndex = list.length - 1;
queue.push(memory);
}
// 闭包
// 将 arguments 作为参数即 args 传入闭包的 add 方法中
(function add( args) {//args[0]即 function(){dataPriv.remove( elem, [ type + "queue", key] ) }
jQuery.each(args, function( _, arg) {if ( isFunction( arg) ) {
// 如果 self 对象没有该方法,将其 push 进 list 中
if (!options.unique || !self.has( arg) ) {list.push( arg);
}
} else if (arg && arg.length && toType( arg) !== "string" ) {
// Inspect recursively
add(arg);
}
} );
} )(arguments);
//undefined undefined
if (memory && !firing) {fire();
}
}
//this 即 self 对象
// 也就说在调用 self 对象内的方法后会返回 self 对象本身
return this;
},
// Remove a callback from the list
remove: function() {jQuery.each( arguments, function( _, arg) {
var index;
while (( index = jQuery.inArray( arg, list, index) ) > -1 ) {list.splice( index, 1);
// Handle firing indexes
if (index <= firingIndex) {firingIndex--;}
}
} );
return this;
},
// Check if a given callback is in the list.
// If no argument is given, return whether or not list has callbacks attached.
has: function(fn) {
return fn ?
jQuery.inArray(fn, list) > -1 :
list.length > 0;
},
// Remove all callbacks from the list
empty: function() {if ( list) {list = [];
}
return this;
},
// Disable .fire and .add
// Abort any current/pending executions
// Clear all callbacks and values
disable: function() {locked = queue = [];
list = memory = "";
return this;
},
disabled: function() {return !list;},
// Disable .fire
// Also disable .add unless we have memory (since it would have no effect)
// Abort any pending executions
lock: function() {locked = queue = [];
if (!memory && !firing) {list = memory = "";}
return this;
},
locked: function() {return !!locked;},
// Call all callbacks with the given context and arguments
fireWith: function(context, args) {if ( !locked) {args = args || [];
args = [context, args.slice ? args.slice() : args ];
queue.push(args);
if (!firing) {fire();
}
}
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 !!fired;}
};
console.log(queue,'queue3614')
return self;
};
解析:
主要看 add()
和fire()
方法
(1)self.add()
注意里面的闭包函数,使用闭包的目的是 冻结 args 的值,这样可以避免异步调用造成的值得改变。
add()方法就是将 function() {dataPriv.remove( elem, [ type + "queue", key] );}
push 进 list 数组中,以供fire()
来调用 list 中的 callback。
注意最后返回的是 this
,即self
对象,也就说在调用 self
对象内的方法后会返回 self
对象本身,而 self
内部又含有 add()、fire()
等方法,通过 jQuery.Callbacks
传入的参数 options
来控制能否调用,及调用的次数。
(2)self.fire()
作用是触发 list 中的回调函数,onece memory
的 once
表示只让 fire()
触发一次后,就需要清理 list,memory
表示是将 list 清空成空数组还是空字符。
八、createOptions()
作用:
将特定格式的 string(空格分开),转化为特定格式的object({xxx:true,xxx:true,...}
.
源码:
// 将特定格式的 string(空格分开),转化为特定格式的 object({xxx:true,xxx:true,...})// Convert String-formatted options into Object-formatted ones
// 源码 3377 行
//'once memory' —> {once:true,memory:true}
function createOptions(options) {var object = {};
jQuery.each(options.match( rnothtmlwhite) || [], function( _, flag) {object[ flag] = true;
} );
return object;
}
解析:
将以空格连接的字符串,以空格拆开,并作为 object 的 key,其 value 为 true
比如:
“once memory” => {once:true,memory:true,}
(完)