Open zhaozy93 opened 7 years ago
队列是一种最基本的数据结构,队列就是一种特殊的线性表,只允许在队尾插入,队头删除,也就是我们常说的先进先出,后进后出。jQuery也实现了这样一种结构,通过shift()和push()对Array进行操作,来模拟队头删除和队尾插入。队列模块最重要的功能之一就是为后面的动画模块提供支持,毕竟很多动画是要按顺序执行,那这时候队列的作用就非常重要了。
shift()
push()
我们在这里这里先讨论一个基本问题,就是每次每个模块好像都有jQuery.extend 和 jQuery.fn.extend,为什么这样呢。 我们可以这样考虑这个问题,jQuery.extend是扩展到jQuery对象上面的,jQuery.fn.extend是扩展到jQuery原型链上面的。 当我们$()获得的对象实例是继承了原型链,但是$并没有继承原型链啊。也就是说jQuery.extend里面的方法,我们可以直接$.queue使用,但是jQuery.fn.extend必须是$().queue使用,虽然函数名是一样的,但完全不是一个东西啊。因为实例之后虽然原型链的东西继承了,但是原来那一套东西是不会跟着过去的。更详细的解释可以看另一篇文章关于new的认识、附带lazyman和currying的理解。 举个例子
function alone(){}; alone.queue = 'abc'; alone.prototype.queue1 = 'qwer'; let a = new alone(); console.log(alone.queue) // "abc" console.log(alone.queue1) // undefined console.log(a.queue) // undefined console.log(a.queue1) // "qwer"
这部分代码也没啥幺蛾子, 整体结构也比较容易理解,维护一个盛放队列的容器,并且对队列实现入列和出列的操作即可。
jQuery.extend({ _mark: function( elem, type ) {}, _unmark: function( force, elem, type ) {}, // 函数入列,并返回队列 queue: function( elem, type, data ) {}, // 函数出列,并执行 dequeue: function( elem, type ) {} }); jQuery.fn.extend({ // 取出函数队列,或者函数入列 queue: function( type, data ) {}, // 函数出列,并执行 dequeue: function( type ) {}, // 清空队列 clearQueue: function( type ) {}, // 观察函数队列和计数器是否完成,并返回异步队列的只读副本 promise: function( type, object ) {} });
在这里我们就可以发现,队列模块是依靠数据缓存模块来维护队列的,将队列分散到各个dom元素或者js对象上,用一个单独的内部属性来作为盛放队列的容器。
queue: function( elem, type, data ) { var q; if ( elem ) { // 修正队列名称,并提供默认队列名称 type = ( type || "fx" ) + "queue"; q = jQuery._data( elem, type ); // 如果没提供入列函数,就直接退出 if ( data ) { // 如果之前没有队列缓存数据,或者传进来的是一个数组,那就将data变为数组然后进行整体存储或者替换, 否则就只是普通的数组push作为入列 if ( !q || jQuery.isArray(data) ) { q = jQuery._data( elem, type, jQuery.makeArray(data) ); } else { q.push( data ); } } return q || []; } },
用于从队列中取出队头的函数并执行
dequeue: function( elem, type ) { // 修正type名 并且查询当前type的队列,并取出列头第一个方法 type = type || "fx"; var queue = jQuery.queue( elem, type ), fn = queue.shift(), hooks = {}; // 如果列头是一个inprogress,则再重新取出一个,这是动画正在执行的占位符号 if ( fn === "inprogress" ) { fn = queue.shift(); } if ( fn ) { // 如果当前type是fx即默认代表动画的队列则再头部插入一个inprogress表示占位符 if ( type === "fx" ) { queue.unshift( "inprogress" ); } // 新建了一个.run结尾的缓存数据,并把一个空对象存进去,引用类型哦 jQuery._data( elem, type + ".run", hooks ); // 执行这个刚刚取出来的函数, 为他增加两个变量, // 第一个封装过的next, 在调用next的时候会再执行jQuery.dequeue就可以保证衔接继续一直出列 // hooks可以存储数据,然后之后的可以读取,相当于是队列前后执行函数进行数据沟通的桥梁 fn.call( elem, function() { jQuery.dequeue( elem, type ); }, hooks ); } if ( !queue.length ) { // 如果队列内没有fn了,则应该清理一下缓存数据 jQuery.removeData( elem, type + "queue " + type + ".run", true ); handleQueueMarkDefer( elem, type, "queue" ); } }
在jQuery中有很多只在内部调用的方法,其名称都是以_开始的。 _mark 和 _unmark特别像计数器,实际上它们也只是为动画部分服务。
// _mark用于增加计数器个数 _mark: function( elem, type ) { if ( elem ) { // 修正type type = ( type || "fx" ) + "mark"; // 新建或者为原本的缓存数据+1 jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 ); } }, // _unmark用于减少计数器个数 _unmark: function( force, elem, type ) { // 可以看出来如果接收的是三个参数,那么顺序是正确的 // 如果接收的参数是两个,那么第一个是元素,第二个是type,force就是false if ( force !== true ) { type = elem; elem = force; force = false; } if ( elem ) { // 修正type type = type || "fx"; var key = type + "mark", // 如果force是true则直接让count变为0, 否则先读区当前的count再减一 count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 ); // 有count值就设置count,没有的话就做删除数据的操作 if ( count ) { jQuery._data( elem, key, count ); } else { jQuery.removeData( elem, key, true ); handleQueueMarkDefer( elem, type, "mark" ); } } },
这一部分可能是更常用的,直接获取完元素之后使用,可能更方便一点。
功能肯定与jQuery.extend中的同名方法类似。
queue: function( type, data ) { // 修正参数, 如果第一个参数不是string则证明只有一个参数,那么data就是要入列的函数, type则就是默认的fx if ( typeof type !== "string" ) { data = type; type = "fx"; } // 如果修正之后都没有data,那就是一个参数也没有,那就是读取第一个元素的fx的数据 if ( data === undefined ) { return jQuery.queue( this[0], type ); } // 只要有data则就为每一个元素都 入列data方法 // 同时 如果是默认的fx动画类型,并且第一个元素不是inprogress的占位符,则自动执行一次出列 return this.each(function() { var queue = jQuery.queue( this, type, data ); if ( type === "fx" && queue[0] !== "inprogress" ) { jQuery.dequeue( this, type ); } }); },
为每个元素都执行一次出列
dequeue: function( type ) { return this.each(function() { jQuery.dequeue( this, type ); }); },
delay: function( time, type ) { // time 有可能是动画的几个string类型 // slow: 600, fast: 200, _default: 400 // 先修正time和type time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; type = type || "fx"; // 入列一个延迟函数, 这个函数里面有next和hooks,这个可以去看之前那个dequeu的方法 // 执行函数传入的第一个参数会是 再次调用一个dequeue以达到next继续调用的效果 return this.queue( type, function( next, hooks ) { var timeout = setTimeout( next, time ); hooks.stop = function() { clearTimeout( timeout ); }; }); },
通过把队列设置为[]来起到置空队列的效果
clearQueue: function( type ) { return this.queue( type || "fx", [] ); },
这个方法的目的不是特别明确,只能看个代码的大概
promise: function( type, object ) { // 如果只有一个元素, 那么应该是object, 但是整个函数内部发现object没有被再次使用到。。。。真神奇 if ( typeof type !== "string" ) { object = type; type = undefined; } // 修正type 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 ] ); } } // 遍历当前this的所有元素, 为每个元素的type + "defer"缓存数据增加一个resolve方法, // 同时这里有个计步器count++ // 这个resolve函数也比较有意思,一定要count为1的时候才会执行 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.Callbacks( "once memory" ), true ) )) { count++; tmp.add( resolve ); } } resolve(); // 返回了这个队列的只读副本 return defer.promise(); }
进过查证,这里有一个bug,最后一行其实是 return defer.promise(object),这样这个异步只读副本就附加到object对象上
return defer.promise(object)
负责检测元素的关联的队列和计数器是否完成,如果完成了就调用一次.promise,这样当前的count就会减一,以来继续判断是不是全部执行完毕。
function handleQueueMarkDefer( elem, type, src ) { // 新建局部变量,并且获取defer函数 var deferDataKey = type + "defer", queueDataKey = type + "queue", markDataKey = type + "mark", defer = jQuery._data( elem, deferDataKey ); // 根据传入的参数来决定 什么样的条件, // 如果有关联的回调函数、计数器和队列才会进入 if ( defer && ( src === "queue" || !jQuery._data(elem, queueDataKey) ) && ( src === "mark" || !jQuery._data(elem, markDataKey) ) ) { // 队列是空的了,并且计步器也是0了,那么久移除关联的回调函数,并且调用一次上面的promise setTimeout( function() { if ( !jQuery._data( elem, queueDataKey ) && !jQuery._data( elem, markDataKey ) ) { jQuery.removeData( elem, deferDataKey, true ); defer.fire(); } }, 0 ); } }
jQuery队列的内部实现也不复杂,就是规范一个固定出入方式的Array,这个Array还是由数据缓存部分提供基础支持。
chapter5 队列Queue
队列是一种最基本的数据结构,队列就是一种特殊的线性表,只允许在队尾插入,队头删除,也就是我们常说的先进先出,后进后出。jQuery也实现了这样一种结构,通过
shift()
和push()
对Array进行操作,来模拟队头删除和队尾插入。队列模块最重要的功能之一就是为后面的动画模块提供支持,毕竟很多动画是要按顺序执行,那这时候队列的作用就非常重要了。整体结构
我们在这里这里先讨论一个基本问题,就是每次每个模块好像都有jQuery.extend 和 jQuery.fn.extend,为什么这样呢。 我们可以这样考虑这个问题,jQuery.extend是扩展到jQuery对象上面的,jQuery.fn.extend是扩展到jQuery原型链上面的。 当我们$()获得的对象实例是继承了原型链,但是$并没有继承原型链啊。也就是说jQuery.extend里面的方法,我们可以直接$.queue使用,但是jQuery.fn.extend必须是$().queue使用,虽然函数名是一样的,但完全不是一个东西啊。因为实例之后虽然原型链的东西继承了,但是原来那一套东西是不会跟着过去的。更详细的解释可以看另一篇文章关于new的认识、附带lazyman和currying的理解。 举个例子
这部分代码也没啥幺蛾子, 整体结构也比较容易理解,维护一个盛放队列的容器,并且对队列实现入列和出列的操作即可。
源码 jQuery.extend
queue
在这里我们就可以发现,队列模块是依靠数据缓存模块来维护队列的,将队列分散到各个dom元素或者js对象上,用一个单独的内部属性来作为盛放队列的容器。
dequeue
用于从队列中取出队头的函数并执行
_mark、_unmark
在jQuery中有很多只在内部调用的方法,其名称都是以_开始的。 _mark 和 _unmark特别像计数器,实际上它们也只是为动画部分服务。
源码 jQuery.fn.extend
这一部分可能是更常用的,直接获取完元素之后使用,可能更方便一点。
queue
功能肯定与jQuery.extend中的同名方法类似。
dequeue
为每个元素都执行一次出列
delay
clearQueue
通过把队列设置为[]来起到置空队列的效果
promise
这个方法的目的不是特别明确,只能看个代码的大概
进过查证,这里有一个bug,最后一行其实是
return defer.promise(object)
,这样这个异步只读副本就附加到object对象上handleQueueMarkDefer
负责检测元素的关联的队列和计数器是否完成,如果完成了就调用一次.promise,这样当前的count就会减一,以来继续判断是不是全部执行完毕。
总结
jQuery队列的内部实现也不复杂,就是规范一个固定出入方式的Array,这个Array还是由数据缓存部分提供基础支持。