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,延迟次数 +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队列的更多相关文章

  1. Spring源代码解析和配置文件载入

    Spring类的继承结构图: Spring运用了大量的模板方法模式和策略模式,所以各位看源代码的时候,务必留意,每个继承的层次都有不同的作用.然后将同样的地方抽取出来,依赖抽象将不同的处理依照不同的策 ...

  2. jQuery源代码解析(1)—— jq基础、data缓存系统

    闲话 jquery 的源代码已经到了1.12.0版本号.据官网说1版本号和2版本号若无意外将不再更新,3版本号将做一个架构上大的调整.但预计能兼容IE6-8的.或许这已经是最后的样子了. 我学习jq的 ...

  3. jQuery源码05 (3653 , 3797) queue() : 队列方法 : 执行顺序的管理

    //对外接口 jQuery.extend({ queue: function( elem, type, data ) {//入队.元素.队列名字.存进去的函数 //jQuery.queue( this ...

  4. jQuery源代码 解析一 工具方法

    1. 外层沙箱以及命名空间$ 几乎稍微有点经验前端人员都这么做,为了避免声明了一些全局变量而污染,把代码放在一个"沙箱执行",然后在暴露出命名空间(可以为API,函数,对象): 2 ...

  5. jQuery源代码阅读之三——jQuery实例方法和属性

    jQuery实例方法及属性相关的代码结构如下 jQuery.fn=jQuery.prototype={ jQuery:core_version, constructor:jQuery, selecto ...

  6. jQuery源代码学习之七—队列模块queue

    一.jQuery种的队列模块 jQuery的队列模块主要是为动画模块EFFECTS提供支持,(不过到现在为了支持动画队列的inprogress的出入队还是搞不太清楚),单独抽取出一个命名空间是为了使程 ...

  7. android7.x Launcher3源代码解析(3)---workspace和allapps载入流程

    Launcher系列目录: 一.android7.x Launcher3源代码解析(1)-启动流程 二.android7.x Launcher3源代码解析(2)-框架结构 三.android7.x L ...

  8. jQuery源代码学习之九—jQuery事件模块

    jQuery事件系统并没有将事件坚挺函数直接绑定在DOM元素上,而是基于事件缓存模块来管理监听函数的. 二.jQuery事件模块的代码结构 //定义了一些正则 // // //jQuery事件对象 j ...

  9. jQuery源代码学习之八——jQuery属性操作模块

    一.jQuery属性模块整体介绍 jQuery的属性操作模块分四个部分:html属性操作,dom属性操作,类样式操作,和值操作. html属性操作(setAttribute/getAttribute) ...

随机推荐

  1. [转]What are mode and status columns under gp_segment_configuration table

    February 16, 2017 10:39 Goal In this article we will try to understand and answer to the below two q ...

  2. H5 设备方向及运动API

    传送门:https://blog.csdn.net/Panda_m/article/details/57515195 入门的demo: <!DOCTYPE html> <html l ...

  3. Android 开发之修改 app 的字体大小(老人模式)

    新的需求(可参见 微信和QQ改变字体): app 字体不随着系统字体大小变化 app 设置中有设置字体大小的开关,变大以后,整个 app 字体变大. 解决方案:(字体需要采用 dp 为单位,不能使用 ...

  4. android:ellipsize="end" 失效或者 相关的Bug

    其实这文章有点傻逼. 相关的问题 TextView android:ellipsize=“end”超出一个字符时不显示…的解决 http://www.pocketdigi.com/20140122/1 ...

  5. 【转】10个非常有用的网页设计工具 | Goodfav Magazine

    10+ very useful Web Designer Tools Totally free legal computer eBooks download, available in various ...

  6. Market Guide for AIOps Platforms

    AIOps platforms enhance IT operations through greater insights by combining big data, machine learni ...

  7. 定期批量修改远程服务器root密码

    一.背景 很多时候运维或安全工作人员需要维护大量的服务器,其中就包括判断是否存在root弱口令, 如果服务器数量很多一一修改的话的确是要好花费不少时间精力的.如果通过脚本来实现密码更改, 再做一个定期 ...

  8. django rest_framework入门五-认证和权限

    1.django User实体 django自带了用户验证模块,django/contrib/auth/models.py定义了用户实体,代码如下: class AbstractUser(Abstra ...

  9. Android 编程下 TextView 添加链接的一种方式

    通过如下这种方式给 TextView 添加的链接支持链接样式.点击事件.href 样式,代码如下: package cn.sunzn.tlink; import android.app.Activit ...

  10. 在XSLT中输出内容带有CDATA的XML节点

    http://www.cnblogs.com/jaxu/archive/2013/03/13/2956904.html **************************************** ...