jQuery源码解析(3)—— ready加载、queue队列
来源:互联网 发布:国产三维制衣软件 编辑:程序博客网 时间:2024/06/06 14:12
ready、queue放在一块写,没有特殊的意思,只是相对来说它俩可能源码是最简单的了。ready是在dom加载完成后,以最快速度触发,很实用。queue是队列,比如动画的顺序触发就是通过默认队列’fx’处理的。
(本文采用 1.12.0 版本进行讲解,用 #number 来标注行号)
ready
很多时候,我们需要尽快的加载一个函数,如果里面含有操作dom的逻辑,那么最好在dom刚刚加载完成时调用。window的load事件会在页面中的一切都加载完毕时(图像、js文件、css文件、iframe等外部资源)触发,可能会因外部资源过多而过迟触发。
DOMContentLoaded
:IE9+、Firefox、Chrome、Safari3.1+、Opera9+
html5规范指定的标准事件,在document上,在形成完整的dom树后就会触发(不理会图像、js文件、css文件等是否下载完毕)。
readystatechange
:IE、Firfox4+、Opera
这个事件的目的是提供与文档或元素的加载状态相关的信息,但这个事件的行为有时候很难预料。支持该事件的每个对象都有一个readyState属性,可能包含下列5个值中的一个。
uninitialized(未初始化):对象存在但尚未初始化
loading(正在加载):对象加载数据完成
interactive(交互):可以操作对象了,但还没有完全加载
complete(完成):对象已经加载完成
对document而言,值为”interactive”的readyState会在与DOMContentLoaded
大致相同时刻触发readystatechange
(行为难料,该阶段既可能早于也可能晚于complete阶段,jq上报告了一个interactive的bug,所以源码中用的complete)。而且在包含较少或较小的外部资源的页面中,readystatechange
有可能晚于load事件,因此优先使用DOMContentLoaded
。
jQuery思路
jq可以通过$(xx).ready(fn)
指定dom加载完后需要尽快调用的事件。我们知道事件一旦错过了监听,就不会再触发,$().ready()增加了递延支持,这里自然要使用'once memory'
的观察者模型,Callback、Deferred对象均可,源码中是一个Deferred对象,同时挂载在变量readyList上。
// #3539jQuery.fn.ready = function( fn ) { // jQuery.ready.promise() 为deferred对象内的promise对象(即readyList.promise()) jQuery.ready.promise().done( fn ); // 链式 return this;};
有了promise对象,需要dom加载完后,尽快的resolve这个promise。判断加载完的方式,就是首先判断是否已经是加载完成状态,如果不是优先使用DOMContentLoaded事件,IE6-8用readystatechange,都要用load事件保底,保证一定触发。由于readystatechange为complete时机诡异有时甚至慢于load,IE低版本可以用定时器反复document.documentElement.doScroll('left')
判断,只有dom加载完成调用该方法才不报错,从而实现尽快的触发。
jQuery是富有极客精神的,绑定的触发函数调用一次后就不再有用,因此触发函数中不仅能resolve那个promise,还会自动解绑触发函数(方法detach()
),这样比如readystatechange、load多事件不会重复触发,同时节省内存。当然doScroll方法是setTimeout完成的,如果被readystatechange抢先触发,需要有变量能告知他取消操作,源码中是jQuery.isReady
。
触发函数->completed()
= 解绑触发函数->detach()
+ resolve那个promise->jQuery.ready()
jq中增加了holdReady(true)
功能,能够延缓promise的触发,holdReady()
不带参数(即jQuery.ready(true))则消减延迟次数,readyWait
初始为1,减至0触发。由于doScroll靠jQuery.isReady防止重复触发,因此即使暂缓jQuery.ready()也要能正常的设置jQuery.isReady = true
。jQuery.ready()不仅能触发promise,之后还会触发’ready’自定义事件。
思路整理
jQuery.fn.ready() -> 供外部使用,向promise上绑定待执行函数jQuery.ready.promise() -> 生成单例promise,绑定事件触发completed()complete() -> 解绑触发函数`detach()` + 无需等待时resolve那个promise`jQuery.ready()`
[源码]
// #3536// readyList.promise() === jQuery.ready.promise()var readyList;jQuery.fn.ready = function( fn ) { // promise后添加回调 jQuery.ready.promise().done( fn ); return this; // 链式};jQuery.extend( { // doScroll需借此判断防止重复触发 isReady: false, // 需要几次jQuery.ready()调用,才会触发promise和自定义ready事件 readyWait: 1, holdReady: function( hold ) { if ( hold ) { // true,延迟次数 +1 jQuery.readyWait++; } else { // 无参数,消减次数 -1 jQuery.ready( true ); } }, // 触发promise和自定义ready事件 ready: function( wait ) { // ready(true)时,消减次数的地方。也能代替干ready()的事 if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { return; } // ready()调用时,标记dom已加载完成 jQuery.isReady = true; // ready()能够设置isReady,只能消减默认的那1次 if ( wait !== true && --jQuery.readyWait > 0 ) { return; } // 触发promise,jQuery.fn.ready(fn)绑定函数都被触发 readyList.resolveWith( document, [ jQuery ] ); // 触发自定义ready事件,并删除事件绑定 if ( jQuery.fn.triggerHandler ) { jQuery( document ).triggerHandler( "ready" ); jQuery( document ).off( "ready" ); } }} );// 解绑函数function detach() { if ( document.addEventListener ) { document.removeEventListener( "DOMContentLoaded", completed ); window.removeEventListener( "load", completed ); } else { document.detachEvent( "onreadystatechange", completed ); window.detachEvent( "onload", completed ); }}// detach() + jQuery.ready()function completed() { // readyState === "complete" is good enough for us to call the dom ready in oldIE if ( document.addEventListener || window.event.type === "load" || document.readyState === "complete" ) { detach(); jQuery.ready(); }}jQuery.ready.promise = function( obj ) { if ( !readyList ) { readyList = jQuery.Deferred(); // 判断执行到这时,是否已经加载完成 if ( document.readyState === "complete" ) { // 不再需要绑定任何监听函数,直接触发jQuery.ready。延迟一会,等代码执行完 window.setTimeout( jQuery.ready ); // Standards-based browsers support DOMContentLoaded } else if ( document.addEventListener ) { // Use the handy event callback document.addEventListener( "DOMContentLoaded", completed ); // 个别浏览器情况,错过了事件仍可触发 window.addEventListener( "load", completed ); // IE6-8不支持"DOMContentLoaded" } else { // Ensure firing before onload, maybe late but safe also for iframes document.attachEvent( "onreadystatechange", completed ); // A fallback to window.onload, that will always work window.attachEvent( "onload", completed ); // If IE and not a frame // continually check to see if the document is ready var top = false; try { top = window.frameElement == null && document.documentElement; } catch ( e ) {} if ( top && top.doScroll ) { ( function doScrollCheck() { // 防止重复触发 if ( !jQuery.isReady ) { try { top.doScroll( "left" ); } catch ( e ) { return window.setTimeout( doScrollCheck, 50 ); } detach(); jQuery.ready(); } } )(); } } } return readyList.promise( obj );};// 执行。生成deferred对象,绑定好监听逻辑jQuery.ready.promise();
queue
jQuery提供了一个多用途队列,animate添加的动画就是使用默认的’fx’队列完成的。动画的特点,是在元素上一经添加,即刻触发,并且该元素一个动画执行完,才会执行下一个被添加的动画。动画的执行是含有异步过程的,从这点上看,queue的价值是允许一系列函数被异步地调用而不会阻塞程序。jq队列的实现,并不是为了仅仅给动画使用,由核心功能jQuery.queue/dequeue
和外观jQuery.fn.queue/dequeue/clearQueue/promise
组成。
queue模型
下面是一个简单的队列模型。如何实现异步调用呢?在栈出的函数fn中传入next参数即可实现,只要函数内调用next(),即可实现异步调用下一个。
// 入队function queue( obj, fn ) { if ( !obj.cache ) obj.cache = []; obj.cache.push(fn);}// 出队function dequeue(obj) { var next = function() { dequeue(obj); } var fn = obj.cache.shift(); if ( fn ) fn(next);}
jquery实现
jquery的实现更精密,还考虑了队列栈出为空后调用钩子函数销毁,type参数省略自动调整。功能自然是两套:jQuery.xx/jQuery.fn.xx
,使得$()
包裹元素可以迭代调用,并且$()调用时type为’fx’时,还将能够添加时即刻执行。储存位置都在私有缓存jQuery._data( elem, type )
中。
API具体功能见下面:
内部使用:(type不存在,则为’fx’,后参数不会前挪) jQuery.queue( elem, type[, fn] )
:向队列添加fn,若fn为数组,则重定义队列,type默认’fx’。这里不会添加_queueHooks jQuery.dequeue( elem, type)
:type默认’fx’,栈出队列开头并执行。若是为’fx’队列,一旦被dequeue过,总是给队列开头增加有一个”inprogress”,之所以这么做是为了满足’fx’动画队列首个添加的函数要立即执行,需要一个标记。还会增加jQuery._queueHooks
钩子,dequeue在队列无函数时调用,会调用钩子来删除队列对象和钩子本身(极客精神-_-||)
外部使用:(type不为字符串,则为’fx’,且后参数会前挪) jQuery.fn.queue( type, fn )
:type默认’fx’,对于’fx’队列,添加第一个fn时默认直接执行(动画添加即执行的原因,第一个添加的开头没有”inprogress”),其他则无此步骤。此方式添加fn都会给元素们的缓存加上用于自毁的钩子jQuery._queueHooks( this, type )
jQuery.fn.dequeue( type )
:对每个元素遍历使用jQuery.dequeue( this, type ) jQuery.fn.clearQueue( type )
:重置队列为空数组,type默认’fx’,不对已绑定的_queuehook产生影响 jQuery.fn.promise( type, obj )
: 返回一个deferred对象的promise对象,带有jQuery._queueHooks
钩子的所有元素钩子均被触发时,触发resolve(比如几个元素动画全都执行完后执行某操作)
在队列中函数执行时,会向函数注入elem、next、hooks,通过next可以让函数内部调用jQuery.dequeue,hooks可以让函数内部调用empty方法直接终止、销毁队列,或者绑定销毁时要执行的逻辑。
[源码]
// #4111,建议:内部使用接口jQuery.extend( { // 有data为设置,无data为读取,都返回该队列 queue: function( elem, type, data ) { var queue; if ( elem ) { type = ( type || "fx" ) + "queue"; queue = jQuery._data( elem, type ); // Speed up dequeue by getting out quickly if this is just a lookup if ( data ) { // data为数组,则直接替换掉原缓存值。原本无值,则指定为空数组 if ( !queue || jQuery.isArray( data ) ) { queue = jQuery._data( elem, type, jQuery.makeArray( data ) ); } else { // 将函数推入队列 queue.push( data ); } } return queue || []; } }, dequeue: function( elem, type ) { type = type || "fx"; var queue = jQuery.queue( elem, type ), startLength = queue.length, fn = queue.shift(), // 单例添加自毁钩子empty方法,并取出 hooks = jQuery._queueHooks( elem, type ), next = function() { jQuery.dequeue( elem, type ); }; /* 1、栈出、执行 */ // 只适用于'fx'队列。凡被dequeue过,开头都是"inprogress",需要再shift()一次 if ( fn === "inprogress" ) { fn = queue.shift(); startLength--; } if ( fn ) { // 'fx'队列,开头加"inprogress"。用于表明队列在运行中,不能立即执行添加的函数 if ( type === "fx" ) { queue.unshift( "inprogress" ); } // 动画中用到的,先不管 delete hooks.stop; // 参数注入,可用来在fn内部递归dequeue fn.call( elem, next, hooks ); } /* 2、销毁 */ // fn不存在,调用钩子销毁队列和钩子本身 if ( !startLength && hooks ) { hooks.empty.fire(); } }, // 自毁钩子,队列无函数时dequeue会触发。存在元素私有缓存上 _queueHooks: function( elem, type ) { var key = type + "queueHooks"; return jQuery._data( elem, key ) || jQuery._data( elem, key, { empty: jQuery.Callbacks( "once memory" ).add( function() { // 销毁队列缓存 jQuery._removeData( elem, type + "queue" ); // 销毁钩子本身 jQuery._removeData( elem, key ); } ) } ); }} );// #4179,用于外部使用的接口jQuery.fn.extend( { queue: function( type, data ) { var setter = 2; /* 1、修正 */ // type默认值为'fx' if ( typeof type !== "string" ) { data = type; type = "fx"; setter--; } /* 2、读取 */ // 无data表示取值,只取this[ 0 ]对应值 if ( arguments.length < setter ) { return jQuery.queue( this[ 0 ], type ); } /* 3、写入 */ return data === undefined ? // 无data,返回调用者 this : this.each( function() { var queue = jQuery.queue( this, type, data ); // 此方法添加,一定会有hooks jQuery._queueHooks( this, type ); // 'fx'动画队列,首次添加函数直接触发 if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { jQuery.dequeue( this, type ); } } ); }, dequeue: function( type ) { // 遍历触发,以支持$(elems).dequeue(type) return this.each( function() { jQuery.dequeue( this, type ); } ); }, // 重置队列为空('fx'队列也没有了"inprogress",添加即触发) clearQueue: function( type ) { return this.queue( type || "fx", [] ); }, // 返回promise。调用者元素们全部缓存中的_queueHooks自毁均触发,才会resolve这个promise promise: function( type, obj ) { var tmp, // 计数,hooks会增加计数值。默认一次,在return前resolve()就会触发这次。 count = 1, defer = jQuery.Deferred(), elements = this, i = this.length, // 消减计数,判断promise是否触发 resolve = function() { if ( !( --count ) ) { defer.resolveWith( elements, [ elements ] ); } }; // 修正type、data if ( typeof type !== "string" ) { obj = type; type = undefined; } type = type || "fx"; while ( i-- ) { // 凡是elem的type对应缓存中带有hook钩子的,都会增加一次计数 tmp = jQuery._data( elements[ i ], type + "queueHooks" ); if ( tmp && tmp.empty ) { count++; // 该队列销毁时会消减增加的这次计数 tmp.empty.add( resolve ); } } resolve(); return defer.promise( obj ); }} );
- jQuery源码解析(3)—— ready加载、queue队列
- jquery源码解析--queue队列
- 【jQuery源码浅析】(五)--文档加载--$.ready
- jQuery中的ready和load事件(源码解析)
- jQuery源码分析-08队列 Queue
- jQuery-1.9.1源码分析系列(六) 延时对象应用——jQuery.ready
- JQuery 加载就绪(ready)
- jQuery源码学习(版本1.11)-ready
- jquery源码解析之ready与load事件
- jQuery 队列queue
- jquery queue()队列学习
- jQuery入门:ready加载问题
- QUEUE——队列(procedure)
- 优先级队列(priority——queue)
- 数据结构——队列(queue)
- jQuery源码之ready()事件
- 加载DOM,jquery比js更快更强——$(document).ready()与Window.onload的对比
- 优先队列——二项队列(binominal queue)
- LinkedList源码解析
- 64位win7下PL/SQLDEVELOPER登陆报ORA-12154:TNS:无法处理服务器名
- 实现Twitter-UI效果
- OpenStack入门 之 管理的资源
- sim900GPRS模块ppp拨号上网
- jQuery源码解析(3)—— ready加载、queue队列
- OpenStack入门 之 基本组件
- 二叉树前序、中序、后序遍历的相互求法
- 数据库分片技术
- scala隐式参数(implicit)
- Android学习之性能工具traceView使用
- 《C++》13 C++ 多态
- mysql删除重复记录,保留最小id的理解
- 实现自己的iOS小咖秀