jquery源码解析:jQuery队列操作queue方法实现的原理
我们先来看一下jQuery中有关队列操作的方法集:
从上图可以看出,既有静态方法,又有实例方法。queue方法,相当于数组中的push操作。dequeue相当于数组的shift操作。举个例子:
function aaa(){
alert(1);
}
function bbb(){
alert(2);
}
$.queue(document,"q1",aaa); //在document下创建一个队列q1,并往q1队列中添加aaa函数。
$.queue(document,"q1",bbb); //在document下取队列q1,并且往q1队列中添加bbb函数。
$.queue(document,"q1",[aaa,bbb]); //跟上面两行代码的效果一样,一次性添加多个函数
console.log($.queue(document,"q1")) //打印出,document下的队列q1的值,结果是:[aaa(),bbb()]
队列中存储的必须是函数。
$.dequeue(document,"q1") //取出队列中的第一项,这里是aaa,然后执行aaa函数,弹出1.
$.dequeue(document,"q1") //取出队列中的第一项,因为aaa已经出队了,所以这时的第一项是bbb,因此执行bbb函数,弹出2。
以上调用的是静态方法,那么实例方法如何调用呢?
$(document).queue("q1",aaa); //入队
$(document).queue("q1",bbb); //入队
$(document).dequeue("q1"); //出队,执行aaa函数。
$(document).dequeue("q1"); //出队执行bbb函数
队列在前端开发中有什么作用呢?请看下面这个例子:
div1的宽度和高度是100,left是0.
$("#div1").click(function(){
$(this).animate({width:300},2000); //通过setInterval来实现,在两秒中,把div1的宽度从100变成300
$(this).animate({height:300},2000); //以此类推
$(this).animate({left:300},2000); //以此类推
})
当点击div1这个元素时,这个元素会先把宽度变成300px(时间2秒),然后再把高度变成300px(时间2秒),最后把left变成300px(时间2秒)。大家知道为什么会这样了,因为animate方法其实就是一个入队和出队操作,入队操作:先把宽度从100到300的函数入队,接着把高度从100到300的函数入队,最后把left从0到300的函数入队。出队操作:先执行队列中的第一个函数,等第一函数执行结束后(把宽度从100变成300),再出队执行第二个函数(把高度从100到300)。以此类推。
队列queue跟Deferred延迟对象有什么区别?Deferred针对一个异步或者简单的异步进行操作,而queue可以针对多个异步进行操作,就比如上面这个例子,队列里面的每一项就是一个异步操作。这种多异步的操作,用Deferred实现很复杂,不可用。当然队列也可以处理同步操作。
另外再举一个例子:
$("#div1").click(function(){
$(this).animate({width:300},2000).animate({left:300},2000);
})
链式操作,也是先把宽度从100变成300之后,再把left从0变成300.
但是$(this).animate({width:300},2000).queue("fx",function(){}).animate({left:300},2000);
只会把宽度从100变成300,然后就不动了。为什么呢?因为queue方法只是入队没有出队,当往"fx"队列中添加了一个空函数时,由于"fx"是animate队列的默认值,所以这时的fx队列有三个函数,当第一个函数执行完之后执行出队操作,这时这个空函数执行,执行完后,没有进行出队操作,所以它后面的函数(把left从0变成300)不会出队执行。
因此我们可以在空函数中进行出队操作$(this).animate({width:300},2000).queue("fx",function(){$(this).dequeue("fx")}).animate({left:300},2000); 这时,把left从0变成300的函数就可以执行了。由于fx是队列中的默认名字,因此上面的代码,可以不用写fx,也就是可以这样写:$(this).animate({width:300},2000).queue(function(){$(this).dequeue()}).animate({left:300},2000);当然也可以这样写:$(this).animate({width:300},2000).queue(function(){next()}).animate({left:300},2000);next方法也是出队的意思。
animate方法还有第三个参数,就是回调方法,比如:
$(this).animate({width:300},2000,function(){}).animate({left:300},2000);当把div的宽度从100变成300时(2秒),就会执行function(){},在执行function里面的代码后,就会立即执行把left从0到300的函数。如果function(){}里面有一个定时器,比如:把高度从100变成300的定时器函数。这时就会出现高度和left同时变化的情况。
由此可知animate的回调方法,不可控,意思就是它的回调方法执行后会立即执行后面的animate中添加的定时器函数。但是queue就不一样,它是可控的,我们可以等function函数执行完成后(也就是说把function里面的把高度从100变成300的定时器函数执行结束后),再调用next方法,这时它后面的animate添加的定时器函数才会执行,也就是进行出队,执行把left从0变成300的定时器函数。能很方便的控制异步操作的流程。
接下来我们来看源码实现:
jQuery.extend({
queue: function( elem, type, data ) {
var queue;
if ( elem ) { //当有元素时,才进行操作。比如:$.queue(document,"q1",aaa);document就是elem
type = ( type || "fx" ) + "queue"; //如果没有传入q1那么type默认为fx,加上queue字符串
queue = data_priv.get( elem, type ); //去数据缓存中获取此元素的q1queue属性值,第一次时,是undefined。
if ( data ) { //data就是aaa
if ( !queue || jQuery.isArray( data ) ) { //如果取的值不存在,第一次时是不存在的。进入if语句
queue = data_priv.access( elem, type, jQuery.makeArray(data) );//把data也就是aaa函数转换成数组形式,也就是变成[aaa()]
} else { //当第二次执行时,也就是$.queue(document,"q1",bbb); 因为里面已经有[aaa()]了,因此直接把data(这里是bbb函数)push到q1queue属性值中。当然这里有一个例外,比如:第二次执行时,是这样的$.queue(document,"q1",[bbb]);传入的data是个数组,这时不会走这里,而是走if语句,这样的话,会把之前的[aaa()]覆盖掉,只会有[bbb()]。
queue.push( data );
}
}
return queue || []; //返回这个队列,其实就是这个数组[aaa(),bbb()]
}
},
dequeue: function( elem, type ) { //$.dequeue(document,"q1")
type = type || "fx";
var queue = jQuery.queue( elem, type ), //先获取这个q1队列的值
startLength = queue.length,
fn = queue.shift(), //取队列中的第一项
hooks = jQuery._queueHooks( elem, type ), //hooks其实是元素elem在数据缓存中的一个属性对象,如果我们调用的是$.dequeue(document,"q1") 的话,那么属性对象名就是q1queueHooks,属性值是{empty: jQuery.Callbacks("once memory").add(function() { data_priv.remove( elem, [ type + "queue", key ] );})}。因此你使用hooks.empty,其实就是q1queueHooks.empty。
next = function() { //这个next方法其实就是出队
jQuery.dequeue( elem, type );
};
if ( fn === "inprogress" ) { //这里为什么会出现inprogress呢?举个例子:$(this).animate({width:300},2000,function(){}).animate({left:300},2000);这个代码的意思是入队两个定时器函数,第一个定时器函数是把宽度从100变成300,第二个定时器函数是把left从0变成300.如果这里只有入队操作,没有出队操作,那么这两个定时器函数都不会执行,因此大家可以去看queue的实例方法,源码在下面,里面有这样一个判断:if ( type === "fx" && queue[0] !== "inprogress" ) {jQuery.dequeue( this, type );},animate的入队,默认队列为fx,而且它的队列的第一项不是inprogress,而是第一个定时器函数,这时进入if语句,进行出队。因此才能执行第一个定时器函数。那么第二个定时器函数来入队时,也会马上出队吗?不会,不然的话,两个定时器函数会同时执行了。那么第二个定时器函数为什么没有立马出队,是因为第一个定时器函数出队时,会在fx队列前面添加inprogress,因此第二个定时器函数入队时,fx队列的第一个项就是inprogress,因而不会进行出队操作。 第一个定时器函数执行完之后,就会进行再次出队,这时第二个定时器函数就会执行了。
fn = queue.shift(); //如果取出的队列的第一项是inprogress,这时队列是[bbb()],因为inprogress已经出队了,就再次出队,这时bbb()出队,队列为[],fn为bbb。
startLength--; //startLength变成1
}
if ( fn ) { //当数组为["inprogress"]出队时,fn = undefined,startLength=0;这时就会结束队列操作了。
if ( type === "fx" ) { //当是默认队列时,也就是animate操作时,就会先往队列的前面添加inprogress
queue.unshift( "inprogress" ); //队列变成 ["inprogress"],这时就会执行bbb(),执行完之后,又出队。
}
delete hooks.stop;
fn.call( elem, next, hooks ); //这里就会执行第一个定时器函数,执行完之后,就会调用next方法,进行出队。这时的队列是["inprogress",bbb()]
}
if ( !startLength && hooks ) { //当队列结束后,清理数据缓存中队列数据
hooks.empty.fire(); //这里执行fire方法,就会触发add添加的方法,也就是data_priv.remove( elem, [ type + "queue", key ] );把缓存数据中的所有队列信息,以及q1queueHooks一起删除掉。
}
},
_queueHooks: function( elem, type ) {
var key = type + "queueHooks";
return data_priv.get( elem, key ) || data_priv.access( elem, key, {
empty: jQuery.Callbacks("once memory").add(function() { //empty的属性值是一个Callbacks对象,Callbacks的特点是可以通过它的add方法添加函数,当调用Callbacks的fire方法时,就会执行add添加的方法。
data_priv.remove( elem, [ type + "queue", key ] );
})
});
}
});
jQuery.fn.extend({
queue: function( type, data ) { // $(document).queue("q1",aaa);
var setter = 2;
if ( typeof type !== "string" ) { //当type不等于字符串时,也就是这种情况时:$(document).queue(aaa);
data = type; //data = aaa,type = "fx",setter =1
type = "fx";
setter--;
}
if ( arguments.length < setter ) { //这里判断是获取,还是设置。比如:$(document).queue("q1"),这里是获取操作,因此进入if语句。
return jQuery.queue( this[0], type ); //获取是针对一组元素的第一个元素。
}
return data === undefined ? this : this.each(function() { //这里就是设置操作,对每个元素都进行设置
var queue = jQuery.queue( this, type, data ); //入队操作,会在缓存系统中添加一个队列q1,队列中,入队aaa。
jQuery._queueHooks( this, type ); //设置元素的hooks对象,会在缓存系统中添加一个hooks属性,它可以移除缓存系统中与元素this,相关的队列操作的所有数据。
if ( type === "fx" && queue[0] !== "inprogress" ) { //跟静态方法的queue的思路一样。
jQuery.dequeue( this, type );
}
});
},
dequeue: function( type ) { //$(document).dequeue("q1"); 出队操作,是针对一组元素的。也就是说如果有多个document被匹配上,那么会对每个document都做出队操作。
return this.each(function() {
jQuery.dequeue( this, type );
});
},
delay: function( time, type ) { //第一个参数是延迟的时间,第二个参数是哪个队列(队列的名字)延迟,我们先来举个例子说下delay方法的作用:$(this).animate({width:300},2000).delay(2000).animate({left:300},2000);这个代码的意思是:第一个定时器函数执行结束后,会延迟两秒钟,才会执行第二个定时器函数。
time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; //jQuery.fx.speeds = {slow: 600,fast: 200,_default: 400};意思就是说,你delay里面是否写了"slow","fast",或"_default"。如果是,就直接调用默认的值,如果传入的是数字,那么就只用数字。
type = type || "fx";
return this.queue( type, function( next, hooks ) {
var timeout = setTimeout( next, time ); //延迟time秒,再进行出队。意思就是time秒后,第二个定时器函数才会执行。
hooks.stop = function() { //这个方法会清除定时器,如果执行,next方法就不会执行,也就不会出队了
clearTimeout( timeout );
};
});
},
clearQueue: function( type ) {
return this.queue( type || "fx", [] ); //把队列变成空数组,上面说到,如果传入数组,会覆盖队列的原数组
},
promise: function( type, obj ) { //type是指队列的名字,如果此type的队列全部出队后,就会执行done添加的方法。我们先举个例子说下这个方法的作用:$(this).animate({width:300},2000).animate({left:300},2000);$(this).promise().done(function(){alert(3)});这句代码的意思是,等上面两个定时器函数都执行结束后(因为他们默认处理的都是fx队列)。才会执行弹出3的函数。
var tmp,
count = 1,
defer = jQuery.Deferred(), //新建一个延迟对象
elements = this,
i = this.length, //元素的个数,这里假设是一个document元素
resolve = function() {
if ( !( --count ) ) {
defer.resolveWith( elements, [ elements ] );
}
};
if ( typeof type !== "string" ) { //如果没传入队列名,就用fx默认队列
obj = type;
type = undefined;
}
type = type || "fx";
while( i-- ) { //执行一次
tmp = data_priv.get( elements[ i ], type + "queueHooks" ); //去缓存系统找跟这个元素有关的数据
if ( tmp && tmp.empty ) { //如果存在,就证明队列中有定时器函数要执行。进入if语句
count++; //count等于2
tmp.empty.add( resolve ); //当调用tmp.empty.fire方法时,就会执行resolve 方法。而这里会等fx类型的队列全部出队后(这两个定时器函数都执行结束后),才会触发fire方法,这时就会执行add添加的所有方法,resolve就是其中一个,于是count就会变成0(在出队列时,下面的resolve方法已经执行一次了),进入if语句,执行延迟对象的resolveWith,而此方法,就会触发延迟对象的done方法添加的函数,因此弹出3的函数执行。
}
}
resolve(); //这里会先执行一次resolve方法,count--,变成1。
return defer.promise( obj ); //返回这个延迟对象。
}
});
这一课的知识点牵涉到前面的Callbacks,Deferred等方法,因此没看懂这两个东西,这个queue当然有看不懂。因此骚年们,加油吧!
加油!
jquery源码解析:jQuery队列操作queue方法实现的原理的更多相关文章
- jQuery 源码分析(十一) 队列模块 Queue详解
队列是常用的数据结构之一,只允许在表的前端(队头)进行删除操作(出队),在表的后端(队尾)进行插入操作(入队).特点是先进先出,最先插入的元素最先被删除. 在jQuery内部,队列模块为动画模块提供基 ...
- jquery源码解析:代码结构分析
本系列是针对jquery2.0.3版本进行的讲解.此版本不支持IE8及以下版本. (function(){ (21, 94) 定义了一些变量和函数, jQuery = function() ...
- jQuery 源码解析二:jQuery.fn.extend=jQuery.extend 方法探究
终于动笔开始 jQuery 源码解析第二篇,写文章还真是有难度,要把自已懂的表述清楚,要让别人听懂真的不是一见易事. 在 jQuery 源码解析一:jQuery 类库整体架构设计解析 一文,大致描述了 ...
- JQuery源码解析(一)
写在前面:本<JQuery源码解析>系列是基于一些前辈们的文章进行进一步的分析.细化.修改而写出来的,在这边感谢那些慷慨提供科普文档的技术大拿们. 要查阅JQ的源文件请下载开发版的JQ.j ...
- [源码解析] 消息队列 Kombu 之 基本架构
[源码解析] 消息队列 Kombu 之 基本架构 目录 [源码解析] 消息队列 Kombu 之 基本架构 0x00 摘要 0x01 AMQP 1.1 基本概念 1.2 工作过程 0x02 Poll系列 ...
- dubbo源码解析五 --- 集群容错架构设计与原理分析
欢迎来我的 Star Followers 后期后继续更新Dubbo别的文章 Dubbo 源码分析系列之一环境搭建 博客园 Dubbo 入门之二 --- 项目结构解析 博客园 Dubbo 源码分析系列之 ...
- jQuery 源码解析(二十六) 样式操作模块 样式详解
样式操作模块可用于管理DOM元素的样式.坐标和尺寸,本节讲解一下样式相关,样式操作通过jQuery实例的css方法来实现,该方法有很多的执行方法,如下: css(obj) ;参数 ...
- jQuery 源码解析(二十九) 样式操作模块 尺寸详解
样式操作模块可用于管理DOM元素的样式.坐标和尺寸,本节讲解一下尺寸这一块 jQuery通过样式操作模块里的尺寸相关的API可以很方便的获取一个元素的宽度.高度,而且可以很方便的区分padding.b ...
- jQuery源码解析资源便签
最近开始解读jQuery源码,下面的链接都是搜过来的,当然妙味课堂 有相关的一系列视频,长达100多期,就像一只蜗牛慢慢爬, 至少品读三个框架,以后可以打打怪,自己造造轮子. 完全理解jQuery源代 ...
随机推荐
- 自己制作winhex的模板
winhex有很多的官方模板,可以在网上下载(后缀tpl)并放至它的安装目录,即可使用.不过要是自己能自己制作,这才好玩,不是么?! 打开模板管理器,可以选中其中一个模板,下面有应用,有编辑,你点开编 ...
- C# \/date(1498820611133+0800)\/ 转DateTime
开发中经常遇到日期转换问题,特别是做接口的时候,现在整理了下时间戳转为C#格式时间的方法: /// <summary> /// 时间戳转为C#格式时间 /// </summary&g ...
- Python之Scrapy遇见个坑
运行Scrapy爬虫被限制抓取,报错: -- :: [scrapy.middleware] INFO: Enabled item pipelines: [] -- :: [scrapy.core.en ...
- Java 设计模式系列(五)原型模式
Java 设计模式系列(五)原型模式 原型模式属于对象的创建模式.通过给出一个原型对象来指明所有创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象.这就是选型模式的用意. 一.原型模 ...
- The left-hand side of an assignment must be a variable,代码中使用了中文的字符
进行ajax测试,报这个错误,代码检测无误,然后就是查了相关文档 发现是符号错误,eclipse识别中文符号,就会报这个错误,而且eclipse的js里需要写冒号结尾,附个代码. <body&g ...
- Oracle学习笔记(十二)
十三.存储过程和存储函数1.掌握存储过程(相当于建立一个函数或者方法体,然后通过外部对其调用) 指存储在数据库中供所有程序调用的子程序叫做存储过程或存储函数. 相同点: 完成特定功能的程序 区别: 是 ...
- selenium设置代理,基于chrome浏览器
工作中遇到需要对项目中使用的selenium设置代理,跟大家分享一下. 1.下载chromeDriver:http://chromedriver.storage.googleapis.com/inde ...
- 从Objective-C到Swift,你必须会的(三)init的顺序
Objective-C的构造函数吧,就最后return一个self.里头你要初始化了什么都可以.在Swift的init函数里把super.init放在前面,然后再初始化你代码里的东西就会报错了. 所以 ...
- 试题 G: 外卖店优先级 第十届蓝桥杯
试题 G: 外卖店优先级时间限制: 1.0s 内存限制: 512.0MB 本题总分: 20 分[问题描述]“饱了么”外卖系统中维护着 N 家外卖店,编号 1 ∼ N.每家外卖店都有一个优先级,初始时 ...
- GlusterFS 三
性能监控 Displaying the I/0 Information $gluster volume profile gv0 startStarting volume profile on gv0 ...