概述
本文详细分析jquery-1.11.1.js源码文件行数:3957~4089;
代码简介:jQuery.queue里使用了data缓存的机制,为elem缓存一个回调队列,每次dequeue时,队列里回调出队的同时会执行,实现了一个回调机制,与Deferred那种相对独立的回调机制相比,这种回调是与elem紧紧关联的,回调函数跟elem绑定在一起;
下文进行详细代码分析。
代码分析
// 定义工具函数queue,dequeue,_queueHooks
jQuery.extend({
// queue方法作用是建立一个数组保存函数,放在与elem相关的data里
// 执行dequeue的时候,函数从数组里取出后会被执行,因此使用queue方法保存的必须是function,不能为其他类型数据
queue: function( elem, type, data ) {
var queue;
if ( elem ) {
// 传入的type作为队列名字,后缀再加个"queue",避免冲突
type = ( type || "fx" ) + "queue";
// 获取对应的数组缓存
queue = jQuery._data( elem, type );
// data不为空则说明是保存数据,为空是查看数据
if ( data ) {
// 两种场景进入if分支,一是前面获取到的queue为空,则jQuery.makeArray(data)会新建一个数组缓存在elem对应的cache里
// 而是如果data是数组,则直接缓存进去,原queue存在的话会被替换掉
if ( !queue || jQuery.isArray(data) ) {
queue = jQuery._data( elem, type, jQuery.makeArray(data) );
// else分支则是往已有数组push新数组
} else {
queue.push( data );
}
}
return queue || [];
}
},
dequeue: function( elem, type ) {
type = type || "fx";
// 获取对应数组队列
var queue = jQuery.queue( elem, type ),
// 队列长度
startLength = queue.length,
// 将queue第一个fn取出
fn = queue.shift(),
hooks = jQuery._queueHooks( elem, type ),
// 定义一个next方法,内容是执行dequeue,next执行相当于递归
next = function() {
jQuery.dequeue( elem, type );
};
// 获取出来的fn是"inprogress"则继续获取
// inprogress是为后面animate方法服务的标志,与其他场景使用队列无关
if ( fn === "inprogress" ) {
fn = queue.shift();
// 队列长度减1
startLength--;
}
if ( fn ) {
// 后续分析animate再分析这里
if ( type === "fx" ) {
queue.unshift( "inprogress" );
}
// clear up the last queue stop function
delete hooks.stop;
// 执行被移除的fn,next作为参数传给fn,因此fn内部可以实现递归效果
fn.call( elem, next, hooks );
}
// startLength为0则执行hooks.empty.fire()从缓存中删除掉队列
if ( !startLength && hooks ) {
hooks.empty.fire();
}
},
// 队列勾子
_queueHooks: function( elem, type ) {
var key = type + "queueHooks";
// 往缓存里增加一个勾子对象,对象里保存一个属性empty(Callbacks对象,用于删除queue和勾子自身)
return jQuery._data( elem, key ) || jQuery._data( elem, key, {
empty: jQuery.Callbacks("once memory").add(function() {
jQuery._removeData( elem, type + "queue" );
jQuery._removeData( elem, key );
})
});
}
});
// 原型扩展queue相关方法
jQuery.fn.extend({
// 实例的queue方法,用于缓存JQ对象的异步函数,出队的时候会被执行
queue: function( type, data ) {
// 表示参数数量,默认设为2
var setter = 2;
// 如果type不是字符串,则默认当做是data来用,而type变为默认值“fx”
if ( typeof type !== "string" ) {
data = type;
type = "fx";
// 原data废弃,因此setter减小1
setter--;
}
// 假设形参长度比setter还小,说明是获取队列
if ( arguments.length < setter ) {
return jQuery.queue( this[0], type );
}
// data如果为undefined,前面返回队列的分支又没有进入,则在这里直接返回JQ对象
return data === undefined ?
this :
// data为有效值,则调用each方法
this.each(function() {
// 回调函数里,对JQ对象的每一个元素,都调用工具queue保存好data
// 注意这里的this跟外面调用each的不同,外面的this是指JQ对象,里面的this在回调被执行时,是JQ对象里的元素
var queue = jQuery.queue( this, type, data );
// 调用一下_queueHooks勾子方法,保证勾子方法内的内容也缓存了,这样在dequeue的时候就会执行勾子方法
jQuery._queueHooks( this, type );
// 这个应该跟后面的animate有关,后续再分析,其他情况的使用不会走进这个分支
if ( type === "fx" && queue[0] !== "inprogress" ) {
jQuery.dequeue( this, type );
}
});
},
// 实例的dequeue,很简单,直接使用each,回调里面使用工具dequeue
dequeue: function( type ) {
return this.each(function() {
jQuery.dequeue( this, type );
});
},
// 实例扩展了一个clearQueue,传空数组作为data达到清空queue效果
clearQueue: function( type ) {
return this.queue( type || "fx", [] );
},
// 返回defer.promise( obj ),obj存在就会将promise对象扩展进去,不存在就直接返回一个promise
// 利用返回的promise可以增加异步回调,当dequeue操作把队列中所有的项都移除后,promise增加的回调函数就会执行
promise: function( type, obj ) {
var tmp,
count = 1,
defer = jQuery.Deferred(),
// 引用JQ对象
elements = this,
// 获取JQ对象的长度
i = this.length,
// 定义resolve函数,里面计算并判断count决定是否执行defer.resolveWith
resolve = function() {
if ( !( --count ) ) {
defer.resolveWith( elements, [ elements ] );
}
};
if ( typeof type !== "string" ) {
obj = type;
type = undefined;
}
type = type || "fx";
// 循环给JQ对象每一个里的每一个元素,获取其勾子对象,往里面添加resolve函数
// 前面代码可知勾子对象里empty是一个Callbacks对象,会在dequeue时判断是否需要执行
// 这样添加进去的resolve也会在dequeue移除完所有内容后被触发,promise的回调就会执行
// 注意这里的queue,queueHooks是JQ对象里的每一个元素都各自有一份的(但里面缓存的回调函数,一般情况下使用实例方法添加的,都是相同的引用)
// 与queue不同,promise回调只执行一次
// 这里的count计数,是要保证JQ对象里的每一个元素都完成了所有出队动作,才会触发
// 一般情况下如果只调用实例方法queue添加回调,count计数是多余的,因为实例方法会循环操作JQ对象全部元素,但是为了保证JQ对象里的一些元素queue里有其他回调,必须使用count
while ( i-- ) {
tmp = jQuery._data( elements[ i ], type + "queueHooks" );
if ( tmp && tmp.empty ) {
// 计数增加
count++;
tmp.empty.add( resolve );
}
}
// 默认count = 1,这里需要先执行一次resolve
resolve();
return defer.promise( obj );
}
});