`
nuysoft
  • 浏览: 516666 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
91bd5d30-bd1e-3b00-9210-87db1cca0016
jQuery技术内幕
浏览量:198732
社区版块
存档分类
最新评论

[原创] jQuery源码分析-08队列 Queue

阅读更多

作者:nuysoft/高云 QQ:47214707 EMail:nuysoft@gmail.com

声明:本文为原创文章,如需转载,请注明来源并保留原文链接。 

 

读读写写,不对的地方请告诉我,多多交流共同进步,本章的的PDF下载在最后。

 

前记:

国庆给自己放了个安静的长假,日游杭州大小景点,夜宿西湖边上,于大街小巷中遍尝美味小吃,没有电脑没有网络,这样的日子真是是好日子啊;回京开始工作了,编程是我的兴趣,虽然变成了工作,但是享受的心态要继续保持下去。

白天工作,不管忙不忙,jQuery源码分析系列只能放在晚上写,经常看的朋友兴许也注意到更新时间一般是凌晨,经常觉的挺累的,想今天算了吧上床睡觉明天再说吧,但还是坚持下来了,我会尽量要求自己1-2天发布一篇。刚开始写的时候,有些担心写出来的东西会幼稚肤浅或讲不清楚,有些地方也确实是这样,这个假期让我想明白了很多事,以后的文章欢迎各位道友拍各种砖和石头,用力点,不要停。

 

8. 队列 Queue

8.1        概述

队列是一种特殊的线性表,只允许在表的前端(队头)进行删除操作(出队),在表的后端(队尾)进行插入操作(入队)。队列的特点是先进先出(FIFO-first in first out),即最先插入的元素最先被删除。

jQuery提供了jQuery.queue/dequeuejQuery.fn.queue/dequeue,实现对队列的入队、出队操作。不同于队列定义的是,jQuery.queuejQuery.fn.queue不仅执行出队操作,返回队头元素,还会自动执行返回的队头元素。

8.2        用途

jQuery源码中,仅用于动画模块,这里入队的函数的功能是:

l  遍历要动画的属性,修正要执行的动画动作,修正/备份属性

l  遍历要动画的属性,为每一个属性创建jQuery.fx对象,计算起始值和结束值,调用fx对象的custom开始动画

看看源码:

/**

 * .animate( properties, [duration], [easing], [complete] )

 *

 * .animate( properties, options )

 *

 * animate做了三件事:

 * 1. 调用jQuery.speed修正传入的参数(时间、算法、回调函数)

 * 2. 遍历要动画的属性,修正要执行的动画动作,修正/备份属性

 * 3. 遍历要动画的属性,为每一个属性创建jQuery.fx对象,计算起始值和结束值,调用fx对象的custom开始动画

 */

animate: function( prop, speed, easing, callback ) {

    var optall = jQuery.speed(speed, easing, callback); // 修正参数

 

    // 如果是空对象,则直接运行callback

    if ( jQuery.isEmptyObject( prop ) ) {

       return this.each( optall.complete, [ false ] );

    }

 

    // Do not change referenced properties as per-property easing will be lost

    // 复制一份prop,不改变原有的属性

    prop = jQuery.extend( {}, prop );

    // queuefalse,动画立即开始,否则则放入动画队列

    return this[ optall.queue === false ? "each" : "queue" ](function() {

       // XXX 'this' does not always have a nodeName when running the

       // test suite

 

       if ( optall.queue === false ) {

           jQuery._mark( this );

       }

 

       var opt = jQuery.extend( {}, optall ), // 复制一份

           isElement = this.nodeType === 1,

           hidden = isElement && jQuery(this).is(":hidden"), // 是否隐藏

           name, val, p,

           display, e,

           parts, start, end, unit;

 

       // will store per property easing and be used to determine when an animation is complete

       // 已完成的属性

       opt.animatedProperties = {};

      

       // 遍历每一个属性,本次遍历仅仅是修正和记录属性值,为后边的动画做准备

       for ( p in prop ) {

           // property name normalization

           // property格式化,转换为驼峰式,因为style的属性名是驼峰式

           name = jQuery.camelCase( p );

           if ( p !== name ) {

              prop[ name ] = prop[ p ];

              delete prop[ p ];

           }

 

           val = prop[ name ];

 

           // easing resolution: per property > opt.specialEasing > opt.easing > 'swing' (default)

           // 样式值允许以数组的方式,数组第一个表示动画是否完成,第二个表示动画的值,这样做有两点好处:

           // 1. 允许在某些属性还未完成时就执行回调函数、执行下一个动画

           // 2. 允许为默写属性指定算法

           if ( jQuery.isArray( val ) ) {

              opt.animatedProperties[ name ] = val[ 1 ]; //

              val = prop[ name ] = val[ 0 ];

           } else {

              // 对指定属性设置动画算法

              opt.animatedProperties[ name ] = opt.specialEasing && opt.specialEasing[ name ] || opt.easing || 'swing';

           }

          

           // 如果属性值是hide,并且已经隐藏,则直接调用回调函数;show同理

           if ( val === "hide" && hidden || val === "show" && !hidden ) {

              return opt.complete.call( this );

           }

 

           // 如果是width/height,修正它的:

           // overflow

           // display

           if ( isElement && ( name === "height" || name === "width" ) ) {

              // Make sure that nothing sneaks out

              // Record all 3 overflow attributes because IE does not

              // change the overflow attribute when overflowX and

              // overflowY are set to the same value

              opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ];

 

              // Set display property to inline-block for height/width

              // animations on inline elements that are having width/height

              // animated

              if ( jQuery.css( this, "display" ) === "inline" &&

                     jQuery.css( this, "float" ) === "none" ) {

                  if ( !jQuery.support.inlineBlockNeedsLayout ) {

                     this.style.display = "inline-block";

 

                  } else {

                     display = defaultDisplay( this.nodeName );

 

                     // inline-level elements accept inline-block;

                     // block-level elements need to be inline with layout

                     if ( display === "inline" ) {

                         this.style.display = "inline-block";

 

                     } else {

                         this.style.display = "inline";

                         this.style.zoom = 1;

                     }

                  }

              }

           }

       }

      

       // 如果是width/height,先设置为超出部分隐藏

       if ( opt.overflow != null ) {

           this.style.overflow = "hidden";

       }

      

       // 遍历每一个属性,

       for ( p in prop ) {

           e = new jQuery.fx( this, opt, p ); // 构造动画fx对象

           val = prop[ p ];

 

           // 如果是toggle/show/hide

           if ( rfxtypes.test(val) ) {

              // 如果是toggle,则判断当前是否hidden,如果hiddenshow,否则hide

              // 如果不是toggle,说明valhide/show之一

              e[ val === "toggle" ? hidden ? "show" : "hide" : val ]();

 

           } else {

              parts = rfxnum.exec( val ); // 数值型,1+=/-=2数值,3单位

              start = e.cur(); // 取出当前值

 

              // 如果是数值型

              if ( parts ) {

                  end = parseFloat( parts[2] ); // 包括了正负号

                  unit = parts[3] || ( jQuery.cssNumber[ p ] ? "" : "px" ); // jQuery.cssNumber中包含的是无单位的数值型属性,比如zIndex/zoom

 

                  // We need to compute starting value

                  // 计算开始值

                  if ( unit !== "px" ) {

                     jQuery.style( this, p, (end || 1) + unit);

                     start = ((end || 1) / e.cur()) * start;

                     jQuery.style( this, p, start + unit);

                  }

 

                  // If a +=/-= token was provided, we're doing a relative animation

                  // 如果以+=/-=开头,则做相对动画

                  if ( parts[1] ) {

                     // 计算出相对值

                     end = ( (parts[ 1 ] === "-=" ? -1 : 1) * end ) + start;

                  }

                  // 开始执行动画

                  e.custom( start, end, unit );

              // 不是数值型

              } else {

                  // 开始执行动画

                  e.custom( start, val, "" );

              }

           }

       }

 

       // For JS strict compliance

       // 严格遵守?

       return true;

    });

}

 

8.3        实现思路

jQuery队列的实现依赖于jQuery.data,用数组实现,作为私有数据存储在jQuery的全局变量jQuery.cache中。

l  调用jQuery.queue入队时,如果不传入队列名,则默认为fx(标准动画)

    n  队列用数组实现,入队直接调用数组对象的方法push

    n  入队的元素必须是函数,或由函数构成的数组

    n  所有队列名会自动加上queue后缀,表示这是一个队列

    n  如果传入的是数组,则覆盖现有的队列

    n  如果不是数组,则直接入队

l  调用jQuery. dequeue出队时,会先调用jQuery.queue取得整个队列,因为队列用数组实现,可以调用数组的shift方法取出第一个元素并执行

    n  执行第一个元素时采用function.call( context, args ),由此可以看出jQuery队列只支持函数(这么说不完全准确,fx动画是个特例,会在队列头端插入哨兵inprogress,类型为字符串)

    n  出队的元素会自动执行,无论这个元素是不是函数,如果不是函数此时就会抛出异常(这个异常并没有处理)

    n  如果队列变成空队列,则用关键delete删除jQuery.cachetype对应的属性

8.4        验证(firefox+firebug

我们验证一下上面的思路:

1.     先入队3个弹窗函数,分别弹出123

$('body').queue( 'test', function(){ alert(1); } )

$('body').queue( 'test', function(){ alert(2); } )

$('body').queue( 'test', function(){ alert(3); } )

2.     查看jQuery.databody分配的唯一id(为什么要查看body的唯一id,请参考数据缓存的解析)

>>> $.expando

"jQuery161017518149125935123"

command: >>> $('body')[0][$.expando]

5

>>> $('body')[0]["jQuery161017518149125935123"]

5

$.expando有三部分构成:字符串"jQuery" + 版本号jQuery.fn.jquery + 随机数Math.random(),因此每次加载页面后都不相同。

3.     查看jQuery.cache对属性5对应的数据,格式化如下:

{

  "1" : { ... },

  "2" : { ... },

  "3" : { ... },

  "4" : { ... },

"5" : {

"jQuery161017518149125935123" : {

  "testqueue" : [

    (function () {alert(1);}),

    (function () {alert(2);}),

    (function () {alert(3);})

]

}

}   

}

内部数据存储在$.expando属性("jQuery161017518149125935123"中,这点区别于普通数据

4.     外事具备,我们出队试试,连续3次调用出队$('body').dequeue( 'test' ),每次调用dequeue后用$('body').queue('test').length检查队列长度

控制台命令

$('body').dequeue( 'test' )

$('body').dequeue( 'test' )

$('body').dequeue( 'test' )

浏览器截图

见附件PDF 见附件PDF 见附件PDF

队列长度

2

1

0

果不其然,调用出队函数dequeue后,入队的函数按照先进先出的顺序,依次被执行

5.     最后看看全部出队后,jQuery.cache中的状态

>>> $.cache[5][$.expando]['testqueue']

undefined

可以看到,testqueue属性已经从body的缓存中移除

8.5        源码分析

jQuery.extend({

    // 计数器,用在动画animate

    _mark: function( elem, type ) {

       if ( elem ) {

           type = (type || "fx") + "mark";

           // 取出数据加1,存储在内部对象上

           jQuery.data( elem, type, (jQuery.data(elem,type,undefined,true) || 0) + 1, true );

       }

    },

    // 用在动画animate

    _unmark: function( force, elem, type ) {

       if ( force !== true ) {

           type = elem;

           elem = force;

           force = false;

       }

       if ( elem ) {

           type = type || "fx";

           var key = type + "mark",

              // 1

              count = force ? 0 : ( (jQuery.data( elem, key, undefined, true) || 1 ) - 1 );

           if ( count ) {

              jQuery.data( elem, key, count, true );

           } else {

              jQuery.removeData( elem, key, true );

              handleQueueMarkDefer( elem, type, "mark" );

           }

       }

    },

    // jQuery.queue( element, [queueName] )

    // 返回在指定的元素element上将要执行的函数队列

   

    // jQuery.queue( element, queueName, newQueue or callback )

    // 修改在指定的元素element上将要执行的函数队列

    // 使用jQuery.queue添加函数后,最后要调用jQuery.dequeue(),使得下一个函数能线性执行

    //

    // 调用jQuery.data,存储为内部数据(pvttrue

    queue: function( elem, type, data ) {

       // elem必须存在,否则没有意义

       if ( elem ) {

           type = (type || "fx") + "queue"; // 改名,每个都要加上queue

           // 取出队列

           var q = jQuery.data( elem, type, undefined, true );

           // Speed up dequeue by getting out quickly if this is just a lookup

           // 如果data存在,才会进行后边转换数组、入队等操作,可以加速取出整个队列

           if ( data ) {

              // 如果队列不存在,或者data是数组,则调用makeArray转换为数组,并覆盖队列(入队)

              if ( !q || jQuery.isArray(data) ) {

                  // 用数组实现队列

                  q = jQuery.data( elem, type, jQuery.makeArray(data), true );

              } else {

                  // 队列存在的话,且data不是数组,直接入队

                  // 这里并没有判断data的类型,不管data是不是函数

                  q.push( data );

              }

           }

           // 返回队列(即入队的同时,返回整个队列)

           // 简洁实用的避免空引用的技巧

           return q || [];

       }

    },

    // 出队并执行

    // 调用jQuery.queue取得整个队列,在调用shift取出第一个元素

    dequeue: function( elem, type ) {

       type = type || "fx"; // 默认fx,但是入队时不是被改为fxqueue了么,别着急!

 

       var queue = jQuery.queue( elem, type ), // 取出队列,调用queuetype变成了type+quque

           fn = queue.shift(), // 取出第一个

           defer;

 

       // If the fx queue is dequeued, always remove the progress sentinel

       // 如果取出的fn是一个正在执行中标准动画fx,抛弃执行哨兵(inprogress),再取一个

       if ( fn === "inprogress" ) {

           fn = queue.shift();

       }

 

       if ( fn ) {

           // Add a progress sentinel to prevent the fx queue from being

           // automatically dequeued

           // 如果是标准动画,则在队列头部增加处理中哨兵属性,阻止fx自动处理

           if ( type === "fx" ) {

              // 在队列头部增加哨兵inprogress

              queue.unshift("inprogress");

           }

           // 执行取出的fn,并传入回调函数jQuery.dequeue

           // 可以看到fn必须是函数,否则会出错

           fn.call(elem, function() {

              // 但是这个回调函数不会自动执行

              jQuery.dequeue(elem, type);

           });

       }

       // 如果执行完毕,则调用jQuery.removeData移除type指定的队列

       // 此时的队列成为空队列,实质是一个空数组,jQuery.removeData内部使用delete关键字删除type对应的空数组

       if ( !queue.length ) {

           jQuery.removeData( elem, type + "queue", true );

           handleQueueMarkDefer( elem, type, "queue" );

       }

    }

});

 

jQuery.fn.extend({

    // queue( [ queueName ] )

    // 返回在指定的元素element上将要执行的函数队列

    //

    // queue( [ queueName ], newQueue or callback )

    // 修改在指定的元素element上将要执行的函数队列

    queue: function( type, data ) {

       // 修正参数:只传了一个非字符串的参数,则默认为动画fx

       if ( typeof type !== "string" ) {

           data = type;

           type = "fx";

       }

       // 如果data等于undefined,则认为是取队列

       if ( data === undefined ) {

           return jQuery.queue( this[0], type );

       }

       // 如果传入了data参数,则在每一个匹配的元素上执行入队操作

       // jQuery中,使用each遍历匹配的元素,是一种安全的惯例做法

       return this.each(function() {

           var queue = jQuery.queue( this, type, data );

           // 如果动画执行完毕(即不是inprogress),则从队列头部移除

           if ( type === "fx" && queue[0] !== "inprogress" ) {

              jQuery.dequeue( this, type );

           }

       });

    },

    // 调用jQuery.dequeue出队

    dequeue: function( type ) {

       return this.each(function() {

           jQuery.dequeue( this, type );

       });

    },

    // Based off of the plugin by Clint Helfers, with permission.

    // http://blindsignals.com/index.php/2009/07/jquery-delay/

    // 延迟执行队列中未执行的函数,通过在队列中插入一个延时 出队的函数来实现

    delay: function( time, type ) {

       time = jQuery.fx ? jQuery.fx.speeds[time] || time : time;

       type = type || "fx";

 

       return this.queue( type, function() {

           var elem = this;

           setTimeout(function() {

              jQuery.dequeue( elem, type );

           }, time );

       });

    },

    // 清空队列,通过将第二参数设置为空数组[]

    clearQueue: function( type ) {

       return this.queue( type || "fx", [] );

    },

    // Get a promise resolved when queues of a certain type

    // are emptied (fx is the type by default)

    // 返回一个只读视图,当队列中指定类型的函数执行完毕后

    promise: function( type, object ) {

       if ( typeof type !== "string" ) {

           object = type;

           type = undefined;

       }

       type = type || "fx";

       var defer = jQuery.Deferred(),

           elements = this,

           i = elements.length,

           count = 1,

           deferDataKey = type + "defer",

           queueDataKey = type + "queue",

           markDataKey = type + "mark",

           tmp;

       function resolve() {

           if ( !( --count ) ) {

              defer.resolveWith( elements, [ elements ] );

           }

       }

       while( i-- ) {

           if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) ||

                  ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) ||

                     jQuery.data( elements[ i ], markDataKey, undefined, true ) ) &&

                  jQuery.data( elements[ i ], deferDataKey, jQuery._Deferred(), true ) )) {

              count++;

              tmp.done( resolve );

           }

       }

       resolve();

       return defer.promise();

    }

});

8.6        总结

jQuery队列的实现并不复杂,它的核心思路是:

在传统队列的实现上增加了出队自动执行,执行完成后再次自动出队。

详见小节:8.3 实现思路

 

3
0
分享到:
评论
4 楼 nuysoft 2013-01-17  
zhumq1982 写道
我想知道 为什么queue 必须原来选择器,如果脱离了选择器 直接 $.queue为什么就不支持了呢
其实我只希望能做一个任务队列,和DOM完全没毛关系~~~

1. jQuery 的 Queue 模块依赖于 Data 模块实现。
2. 简单的任务队列可以通过数组实现,push() 入队,shift() 出队。
3 楼 zhumq1982 2013-01-17  
我想知道 为什么queue 必须原来选择器,如果脱离了选择器 直接 $.queue为什么就不支持了呢
其实我只希望能做一个任务队列,和DOM完全没毛关系~~~
2 楼 abCity 2012-02-07  
太棒了...!!!
1 楼 rambolovepanda 2011-12-20  
thank you very much!

相关推荐

    jQuery源码分析

    jQuery源码分析 00 前言开光 01 总体架构 03 构造jQuery对象-源码结构和核心函数 03 构造jQuery对象-工具函数 05 异步队列 Deferred 08 队列 Queue 09 属性操作 10 事件处理-Event-概述和基础知识 15 AJAX-前置过滤...

    jQuery 源码分析笔记(7) Queue

    每个Element可以拥有多个队列,但是基本上都只使用到一个,即默认的fn队列。队列允许一系列函数被...同时,queue可以指定队列名称获得其他能力,而不局限于fx队列。 代码如下: // 一般用法: $(“#foo”).slideUp(fun

    jQuery 1.9.1源码分析系列(十五)之动画处理

    jQuery 1.9.1源码分析系列(十五)动画处理之缓动动画核心Tween ://www.jb51.net/article/75821.htm a.动画入口jQuery.fn.animate函数执行流程详解 ——————————————————————————–  ...

    jQuery 1.9.1源码分析系列(十四)之常用jQuery工具

    queue(([ queueName ] [, newQueue ]) || ([ queueName ,] callback ))(获取或设置当前匹配元素上待执行的函数队列. 如果当前jQuery对象匹配多个元素:获取队列时,只获取第一个匹配元素上的队列;设置队列(替换...

    jQuery技术内幕 深入解析jQuery架构设计与实现原理

    接着详细分析了底层支持模块的源码实现,包括:选择器sizzle、异步队列deferred、数据缓存data、队列queue、浏览器功能测试support;最后详细分析了功能模块的源码实现,包括:属性操作attributes、事件系统events、...

    jQuery技术内幕:深入解析jQuery架构设计与实现原理

    第三部分(第3~7章)详细分析了底层支持模块的源码实现,包括选择器Sizzle、异步队列Deferred Object、数据缓存Data、队列Queue、浏览器功能测试Support。 第四部分(第8~14章)详细分析了功能模块的源码实现,...

    JQUERY技术内幕:深入解析QUERY架构设计与实现原理 完整版 共两个包

    接着详细分析了底层支持模块的源码实现,包括:选择器sizzle、异步队列deferred、数据缓存data、队列queue、浏览器功能测试support;最后详细分析了功能模块的源码实现,包括:属性操作attributes、事件系统events、...

    jquery技术内幕:深入解析jquery架构设计与实现原理 完整版第二个包

    接着详细分析了底层支持模块的源码实现,包括:选择器sizzle、异步队列deferred、数据缓存data、队列queue、浏览器功能测试support;最后详细分析了功能模块的源码实现,包括:属性操作attributes、事件系统events、...

    最新Python3.5零基础+高级+完整项目(28周全)培训视频学习资料

    队列Queue 作业之主机批量管理 第10周 心灵分享 上节回顾 多进程 多进程Queue 多进程Pipes与Manager 进程锁与进程池详解 协程 协程Gevent 协程之爬虫 协程之Socket IO多路复用 IO模式 Select解析Socket通信 作业 ...

Global site tag (gtag.js) - Google Analytics