Queue队列:
队列是一种特殊的线性表,只允许在表的前端(队头)进行删除操作(出队),在表的后端(队尾)进行插入操作(入队)。队列的特点是先进先出(FIFO-first in first out),即最先插入的元素最先被删除。
jQuery提供了jQuery.queue/dequeue和jQuery.fn.queue/dequeue,实现对队列的入队、出队操作。不同于队列定义的是,jQuery.queue和jQuery.fn.queue不仅执行出队操作,返回队头元素,还会自动执行返回的队头元素。
queue是用来维护函数队列的。比较常用的是queue(queueName, callback);其中queueName缺省是fn,即标准函数队列。 每个Element可以拥有多个队列,但是基本上都只使用到一个,即默认的fn队列。队列允许一系列函数被异步地调用而不会阻塞程序。 例如:$("#foo").slideUp().fadeIn();其实这个就是我们大家常用的链式调用,实际上这是一个Queue。所以队列和Deferred地位类似, 是一个内部使用的基础设施。当slideUp运行时,fadeIn被放到fx队列中,当slideUp完成后,从队列中被取出运行。queue函数允许 直接操作这个链式调用的行为。同时,queue可以指定队列名称获得其他能力,而不局限于fx队列
1 // 一般用法: 2 $("#foo").slideUp(function() { 3 alert("Animation complete."); 4 }); 5 // 相当于: 6 $("#foo").slideUp(); // 不提供回调,只是触发事件 7 $("#foo").queue(function() { // 把回调函数加入 8 alert("Animation complete."); 9 $(this).dequeue(); // 必须从队列中取出,那么队列中的下一个函数就有机会被调用 10 });
调用时,如果不传入队列名,则默认为fx(标准动画)
1>>队列用数组实现,入队直接调用数组对象的方法 push入队
2>>入队必须是函数,或函数数组
3>>所有队列名加上queue后缀,表示这是一个队列
4>>如果传入的是数组,覆盖现有队列
5>>如果不是数组,则直接入队
1. 调用jQuery. dequeue出队时,会先调用jQuery.queue取得整个队列,因为队列用数组实现,可以调用数组的shift方法取出第一个元素并执行
2. 执行第一个元素时采用function.call( context, args ),由此可以看出jQuery队列只支持函数(这么说不完全准确,fx动画是个特例,
会在队列头端插入哨兵inprogress,类型为字符串)
3. 出队的元素会自动执行,无论这个元素是不是函数,如果不是函数此时就会抛出异常(这个异常并没有处理)
4. 如果队列变成空队列,则用关键delete删除jQuery.cache中type对应的属性
示例:
1. 先入队3个弹窗函数,分别弹出1、2、3
1 $('body').queue( 'test', function(){ alert(1); } ) 2 $('body').queue( 'test', function(){ alert(2); } ) 3 $('body').queue( 'test', function(){ alert(3); } )
2. 查看jQuery.data为body分配的唯一id(为什么要查看body的唯一id,请参考数据缓存的解析)
1 $.expando : "jQuery161017518149125935123" 2 3 command: >>> $('body')[0][$.expando]
$('body')[0]["jQuery161017518149125935123"]
$.expando有三部分构成:字符串"jQuery" + 版本号jQuery.fn.jquery + 随机数Math.random(),因此每次加载页面后都不相同。
3. 查看jQuery.cache对属性5对应的数据,格式化如下:
1 { 2 "1" : { ... }, 3 "2" : { ... }, 4 "3" : { ... }, 5 "4" : { ... }, 6 "5" : { 7 "jQuery161017518149125935123" : { 8 "testqueue" : [(function () {alert(1);}),(function () {alert(2);}),(function () {alert(3);})] 9 } 10 } 11 }
内部数据存储在$.expando属性("jQuery161017518149125935123")中,这点区别于普通数据
4.连续3次调用出队$('body').dequeue( 'test' ),
每次调用dequeue后用$('body').queue('test').length检查队列长度 控制台命令
1 $('body').dequeue( 'test' ); 2 console.log($('body').dequeue( 'test' ).length);//2 3 4 $('body').dequeue( 'test' ); 5 console.log($('body').dequeue( 'test' ).length);//1 6 7 $('body').dequeue( 'test' ); 8 console.log($('body').dequeue( 'test' ).length);//0
调用出队函数dequeue后,入队的函数按照先进先出的顺序,依次被执行
5. 最后看看全部出队后,jQuery.cache中的状态
1 $.cache[5][$.expando]['testqueue'] //undefined
可以看到,testqueue属性已经从body的缓存中移除
jQuery.queue(element,[queueNmae]):返回在指定的元素element上将要执行的函数队列
jQuery.queue(element,queueName,newQueue or callback):修改在指定的元素element上将要执行的函数队列
使用jQuery.queue()添加函数后,最后要调用jQuery.queue(),使得下一个函数能线性执行
调用jQuery.data 存储为内部数据(pvt 为true)
1 queue:function(elem,type,data){ 2 "jQuery161017518149125935123" : { 3 "testqueue" : [(function () {alert(1);}),(function () {alert(2);}),(function () {alert(3);})] 4 }
queue内部使用data或者JavaScript数组API来保存数据。其中操作数组的push和shift天生就是一组队列API。而data可以用来保存任意数据。
源码分析:
1 jQuery.extend({ 2 //计数器 用在animate中 3 _mark:function(elem,type){ 4 if(elem){ 5 type = (type || "fx") + "mark"; 6 //取出数据加1 存储在内部对象上 7 jQuery.data(elem,type,(jQuery.data(elem,type,undefined,true) || 0)+1,true); 8 } 9 }, 10 //用在animate中 减一 11 _unmark:function(force,elem,type){ 12 if(force !==true){ 13 type = elem; 14 elem = force; 15 force = false; 16 } 17 if(elem){ 18 type = type || "fx"; 19 var key = type + "mark", 20 count = force ? 0 :((jQuery.data(elem,key,undefined,true) || 1) -1); 21 if(count){ 22 jQuery.data(elem,key,count,true); 23 }else{ 24 jQuery.removeData(elem,key,true); 25 handleQueueMarkDefer(elem,type,"mark"); 26 } 27 } 28 }, 29 //elem必须存在 30 if(elem){ 31 //默认fn:为type或者fx type相当于例子中的test 32 type = (type || "fx") + "queue";//属性名加上queue 33 } 34 //取出队列 data 内部API:data(elem,key,value,pvt); 35 //这里不传入value 只是取队列。 36 var q = jQuery.data(elem,type,undefined,true); 37 //如果data存在,才会进行后边的转换数组 入队等操作 38 if(data){ 39 //队列不存在或者data是数组 可以makeArray转换 40 if(!q || jQuery.isArray(data){ 41 //数组实现队列 type的属性值为jQuery.makeArray(data)数组 42 q = jQuery.data(elem,type,jQuery.makeArray(data),true); 43 }else{ 44 //如果不是数组,则直接入队 45 q.push(data); 46 } 47 } 48 //返回队列 即入队同时返回整个队列 49 //简洁实用的避免空引用的技巧 50 return q || []; 51 }, 52 //出队并执行 调用jQuery.queue取的整个队列 再调用shift取出第一个元素来执行 53 dequeue:function(elem,type){ 54 type = type || "fx";//默认为fx 入队被改为fxqueue了? 55 //得到这个队列 执行这句 var q = jQuery.data(elem,type,undefined,true); 56 var queue = jQuery.queue(elem,type),//取出队列 57 58 fn = queue.shift(),//取出第一个 shift():用于把数组的第一个元素从其中删除,并返回第一个元素的值 59 defer; 60 //"inprogress"岗哨 如果第一个元素时岗哨 61 if(fn === "inprogress"){//如果取出来的fn是一个正在执行中的标准动画fx,抛弃执行哨兵(inprogress) 重新取 62 fn = queue.shift();//这里是取第二个作为fn的值,因为第一个取到的是正在执行的fx 63 } 64 65 if(fn){ 66 //如果是标准动画,则在队列头部增加处理中哨兵属性,阻止fx自动执行 67 if(type === "fx"){ 68 //在队列头部增加哨兵 inprogress unshift():可向数组的开头添加一个或更多元素 69 queue.unshift("inprogress");//标准动画 在第一个位置插入inprogress 70 } 71 //fn函数 72 fn.call(elem,function(){ 73 //这个回调函数不会自动执行 没有return 74 jQuery.dequeue(elem,type); 75 }); 76 } 77 if(!queue.length){//删除缓存的数据 78 jQuery.removeData(elem,type + "queue",true);//内部使用delete删除type对应的空数组 79 handleQueueMarkDefer(elem,type,"queue"); 80 } 81 } 82 }); 83 jQuery.fn.extend({ 84 //queue([queueName])返回指定元素上将要执行的函数队列 85 //queue([queueName],callback)修改指定元素上将要执行的函数队列 86 queue:function(type,data){ 87 //只传了一个非字符串的参数 则默认为动画fx 88 if(typeof type !== "string"){ 89 data = type; 90 type = "fx"; 91 } 92 //data等于undefined 认为是取队列 93 if(data === undefined){//取值 94 return jQuery.queue(this[0],type); 95 } 96 //如果传入了data 在每一个匹配的元素上执行入队操作 97 return this.each(function(){//修改 98 var queue = jQuery.queue(this,type,data); 99 //如果动画执行完毕(且不是inprogress)从队列头部移除 100 if(type === "fx" && queue[0] !== "inprogree"){ 101 jQuery.dequeue(this,type);//移除 102 } 103 }); 104 }, 105 //调用jQuery.dequeue出队 106 dequeue:function(type){ 107 return this.each(function(){ 108 jQuery.dequeue(this,type); 109 } 110 }, 111 //延迟执行队列中未执行的函数,通过在队列中插入一个时延出队的函数来实现 112 delay:function(time,type){ 113 time = jQuery.fx ? jQuery.fx.speeds[time] || time :time; 114 type = type || "fx"; 115 return this.queue(type,function(){ 116 var elem = this; 117 setTimeout(function(){ 118 jQuery.dequeue(elem,type); 119 },time); 120 } 121 }, 122 //清空队列,通过将第二个参数设为空数组[] 123 clearQueue:function(type){ 124 return this.queue(type || "fx",[]); 125 }, 126 //返回一个只读视图,当队列中指定类型的函数执行完毕后 127 promise:function(type,object){ 128 //type不是字符串,值object是type 129 if(typeof type !== "string"){ 130 object = type; 131 type = undefined; 132 } 133 type = type || "fx"; //取type的值 或者是fx 134 var defer = jQuery.Deferred(), 135 elements = this, 136 i = elements.length, 137 count =1, 138 deferDataKey = type + "defer", 139 queueDataKey = type + "queue", 140 markDataKey = type + "mark", 141 tmp; 142 function resolve(){ 143 if(!(--count)){//如果count=0 执行队列回调函数函数使用指定上下文,context :elements,args:elements 144 defer.resolveWith(elements,[elements]); 145 } 146 } 147 while( i-- ) { 148 //延迟队列 或者执行队列 或者标记队列? 149 if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) || 150 ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) || 151 jQuery.data( elements[ i ], markDataKey, undefined, true ) ) && 152 jQuery.data( elements[ i ], deferDataKey, jQuery._Deferred(), true ) )) { 153 count++; 154 tmp.done( resolve );// 155 } 156 } 157 resolve(); 158 return defer.promise(); 159 } 160 });