jQuery源码分析系列:队列操作

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     });
原文地址:https://www.cnblogs.com/colorstory/p/2612762.html