jQuery源代码解析(3)—— ready载入、queue队列

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 );
    }
} );
时间: 2024-10-10 06:03:18

jQuery源代码解析(3)—— ready载入、queue队列的相关文章

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

闲话 jquery 的源代码已经到了1.12.0版本号.据官网说1版本号和2版本号若无意外将不再更新,3版本号将做一个架构上大的调整.但预计能兼容IE6-8的.或许这已经是最后的样子了. 我学习jq的时间非常短,应该在1月.那时的版本号还是1.11.3,通过看妙味课堂的公开课视频和文档里的全部api的注解学习. 源代码则是近期些日子直接生啃.跳过了sizzle和文档处理的部分(待业狗压力大.工作以后再看).关注data.ready.event.queue.Defferred(jq的promise

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

//对外接口 jQuery.extend({ queue: function( elem, type, data ) {//入队.元素.队列名字.存进去的函数 //jQuery.queue( this, type, function( next, hooks ) {}) var queue; if ( elem ) { type = ( type || "fx" ) + "queue";//不写队列名字就是fx // $.queue( document , 'q1'

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

Launcher系列目录: 一.android7.x Launcher3源代码解析(1)-启动流程 二.android7.x Launcher3源代码解析(2)-框架结构 三.android7.x Launcher3源代码解析(3)-workspace和allapps载入流程 前两篇博客分别对Lancher的启动和Launcher的框架结构进行了一些分析.这一篇.将着重開始分析界面的载入流程. 1.总体流程 先上一张总体的流程图吧.(图片看不清能够下载下来看或者右击新开个页面查看图片) wate

jQuery源代码学习之六——jQuery数据缓存Data

一.jQuery数据缓存基本原理 jQuery数据缓存就两个全局Data对象,data_user以及data_priv; 这两个对象分别用于缓存用户自定义数据和内部数据: 以data_user为例,所有用户自定义数据都被保存在这个对象的cache属性下,cache在此姑且称之为自定义数据缓存: 自定义数据缓存和DOM元素/javascript对象通过id建立关联,id的查找通过DOM元素/javascript元素下挂载的expando属性获得 话不多说,直接上代码.相关思路在代码注释中都有讲解

NIO框架之MINA源代码解析(二):mina核心引擎

NIO框架之MINA源代码解析(一):背景 MINA的底层还是利用了jdk提供了nio功能,mina仅仅是对nio进行封装.包含MINA用的线程池都是jdk直接提供的. MINA的server端主要有accept.processor.session三部分组成的.当中accept主要负责在指定的port监听.若有新连接则建立一个新的session.processor则负责处理session相应的发送数据和接收数据并调用上层处理:session则缓存当前连接数据. MINA採用了线程懒启动的技术,即

Spark技术内幕:Client,Master和Worker 通信源代码解析

Spark的Cluster Manager能够有几种部署模式: Standlone Mesos YARN EC2 Local 在向集群提交计算任务后,系统的运算模型就是Driver Program定义的SparkContext向APP Master提交,有APP Master进行计算资源的调度并终于完毕计算.具体阐述能够阅读<Spark:大数据的电花火石!>. 那么Standalone模式下,Client.Master和Worker是怎样进行通信,注冊并开启服务的呢? 1. node之间的RP

[源码]源代码解析 SynchronousQueue

简析SynchronousQueue,LinkedBlockingQueue,ArrayBlockingQueue 三者都是blockingQueue. LinkedBlockingQueue,ArrayBlockingQueue 有界,默认是Integer.Max; SynchronousQueue没什么界不界的概念.之所以这么说.是因为它的操作必须成对. 注记方案: oppo(oppo手机)是一对,offer和pool不阻塞 ppt是一对.put和take都阻塞. 1. 里面没有任何数据.调

Android源代码解析之(七)--&amp;gt;LruCache缓存类

转载请标明出处:一片枫叶的专栏 android开发过程中常常会用到缓存.如今主流的app中图片等资源的缓存策略通常是分两级.一个是内存级别的缓存,一个是磁盘级别的缓存. 作为android系统的维护者google也开源了其缓存方案,LruCache和DiskLruCache.从android3.1開始LruCache已经作为android源代码的一部分维护在android系统中.为了兼容曾经的版本号android的support-v4包也提供了LruCache的维护,假设App须要兼容到andr

SDWebImage源代码解析(二)

上一篇:SDWebImage源代码解析(一) 2.缓存 为了降低网络流量的消耗.我们都希望下载下来的图片缓存到本地.下次再去获取同一张图片时.能够直接从本地获取,而不再从远程server获取.这样做的还有一个优点是提升了用户体验,用户第二次查看同一幅图片时,能高速从本地获取图片直接呈现给用户. SDWebImage提供了对图片缓存的支持,而该功能是由SDImageCache类来完毕的.该类负责处理内存缓存及一个可选的磁盘缓存.当中磁盘缓存的写操作是异步的,这样就不会对UI操作造成影响. 2.1