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上。

// #3539
jQuery.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,延迟次数 +1jQuery.readyWait++;} else {// 无参数,消减次数 -1jQuery.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 oldIEif ( 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 callbackdocument.addEventListener( "DOMContentLoaded", completed );// 个别浏览器情况,错过了事件仍可触发window.addEventListener( "load", completed );// IE6-8不支持"DOMContentLoaded"} else {// Ensure firing before onload, maybe late but safe also for iframesdocument.attachEvent( "onreadystatechange", completed );// A fallback to window.onload, that will always workwindow.attachEvent( "onload", completed );// If IE and not a frame// continually check to see if the document is readyvar 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 lookupif ( 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内部递归dequeuefn.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 );// 此方法添加,一定会有hooksjQuery._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这个promisepromise: 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、dataif ( 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队列相关推荐

  1. android资源加载流程6,FrameWork源码解析(6)-AssetManager加载资源过程

    之前一段时间项目比较忙所以一直没有更新,接下来准备把插件化系列的文章写完,今天我们就先跳过ContentProvider源码解析来讲资源加载相关的知识,资源加载可以说是插件化非常重要的一环,我们很有必 ...

  2. 从源码解析-结合Activity加载流程深入理解ActivityThrad的工作逻辑

    ActivityThread源码解析 前言 类简称 类简介 一 二 三 四 五 代理和桩的理解 ActivityThread ActivityThread.main AT.attach AMN.get ...

  3. Spring源码解析-applicationContext.xml加载和bean的注册

    applicationContext文件加载和bean注册流程 ​ Spring对于从事Java开发的boy来说,再熟悉不过了,对于我们这个牛逼的框架的介绍就不在这里复述了,Spring这个大杂烩,怎 ...

  4. 有没有code能改xml内容_Spring源码解析-applicationContext.xml加载和bean的注册

    applicationContext文件加载和bean注册流程 ​ Spring对于从事Java开发的boy来说,再熟悉不过了,对于我们这个牛逼的框架的介绍就不在这里复述了,Spring这个大杂烩,怎 ...

  5. 【框架源码】Spring源码解析之BeanDefinition加载流程解析

    观看本文之前,我们先思考一个问题,Spring是如何描述Bean对象的? Spring是根据BeanDefinition来创建Bean对象,BeanDefinition就是Spring中表示Bean定 ...

  6. jQuery源码解析(架构与依赖模块)

    jQuery设计理念 引用百科的介绍: jQuery是继prototype之后又一个优秀的Javascript框架.它是轻量级的js库 ,它兼容CSS3,还兼容各种浏览器(IE 6.0+, FF 1. ...

  7. jQuery源码解析(架构与依赖模块)第一章 理解架构

    1-1 jQuery设计理念 引用百科的介绍: jQuery是继prototype之后又一个优秀的Javascript框架.它是轻量级的js库 ,它兼容CSS3,还兼容各种浏览器(IE 6.0+, F ...

  8. jquery源码解析:代码结构分析

    本系列是针对jquery2.0.3版本进行的讲解.此版本不支持IE8及以下版本. (function(){ (21, 94)     定义了一些变量和函数,   jQuery = function() ...

  9. JQuery 源码解析资料

    2019独角兽企业重金招聘Python工程师标准>>> jQuery源码分析系列目录 jQuery源码解读-理解架构 jQuery源码解析(架构与依赖模块) jQuery v1.10 ...

  10. Android 11.0 Settings源码分析 - 主界面加载

    Android 11.0 Settings源码分析 - 主界面加载 本篇主要记录AndroidR Settings源码主界面加载流程,方便后续工作调试其流程. Settings代码路径: packag ...

最新文章

  1. 桌子上有个盘子_日本留学生活:留学生在餐厅刷盘子的传闻,竟然在自己身上上演...
  2. Javascript s02
  3. JavaScript中的常量:什么时候使用它,有必要吗?
  4. WIN7无法记住远程登录密码
  5. Microsoft Sync Framework 系列(三):微软同步框架出现背景及要解决的问题
  6. 每日一笑 | 坐牢吗?学编程那种~
  7. 交互式多模型_论文深度提升的万金油方法——多属性决策 Ⅱ
  8. c语言函数实际参数,C语言:函数声明与定义的参数不一致问题,后果可能很严重哦!!!!!...
  9. iOS 使用UILocalizedIndexedCollation实现区域索引标题(Section Indexed Title)即拼音排序...
  10. Lyft无人车战略揭秘:两个团队并行,用百度Apollo,想定行业标准
  11. python的setheading什么意思_用Python告诉你什么是佩奇
  12. 完美卸载SQL Server 2008的方案
  13. GUI开发和JDBC编程实现员工管理
  14. 为什么计算机休眠风扇还转,Win10电脑睡眠但风扇还在转怎么办
  15. win2003服务器性能工具,win2003服务器安全一键配置工具
  16. Dapr for dotnet | 并发计算模型 - Virtual Actors
  17. Codecademy 你值得拥有,非常棒的编程学习网站
  18. 秋叶收藏集, 动态规划 leetcode LCP 19
  19. 业务后台商业组件ViewUI(iView)入门
  20. 动态数码管显示(单片机)

热门文章

  1. CVPR2020| 最新CVPR2020论文抢先看,附全部下载链接!
  2. 太神奇!波士顿动力又出新视频!Spot 机器狗这次竟学会了跳绳...
  3. 卡卡半智能扫地机器人_扫地机器人哪个牌子好?精选五款高智能的扫地机器人...
  4. 基于无监督深度学习的单目视觉的深度和自身运动轨迹估计的深度神经模型
  5. 什么!卷积要旋转180度?!
  6. 你知道应聘上一份机器学习的工作需要哪些条件吗?
  7. sorted函数python_python中排序函数sort,sorted和operator.itemgetter的使用
  8. mysql5.7 索引
  9. bee 字符串转int_beego中gbk和utf8编码转换问题
  10. tecplot对数坐标轴怎么画_一次更换双速风机接线经历,想通了改变磁极对数原理...