软件世界网 购物 网址 三丰软件 | 小说 美女秀 图库大全 游戏 笑话 | 下载 开发知识库 新闻 开发 图片素材
多播视频美女直播
↓电视,电影,美女直播,迅雷资源↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
移动开发 架构设计 编程语言 Web前端 互联网
开发杂谈 系统运维 研发管理 数据库 云计算 Android开发资料
  软件世界网 -> Web前端 -> jQuery源码解析(3)——ready加载、queue队列 -> 正文阅读

[Web前端]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 );
    }
} );

......显示全文...
    点击查看全文


上一篇文章      下一篇文章      查看所有文章
2016-04-01 16:48:17  
Web前端 最新文章
10分钟
SSM框架SSM项目源码SSM源码下载java框架整合
javascript入门
JavaScript常用对象Array(2)
8.Smarty3:模版中的内置函数
表单脚本
iTextSharp5.0页眉页脚及Asp.net预览的实现
MVC基础学习—理论篇
JavaScript
http协议中get与post区别详解
360图书馆 软件开发资料 文字转语音 购物精选 软件下载 美食菜谱 新闻资讯 电影视频 小游戏 Chinese Culture
生肖星座 三丰软件 视频 开发 Android开发 站长 古典小说 网文精选 搜图网 美图 中国文化英文版 多播 租车 短信
2017-7-26 10:44:03
多播视频美女直播
↓电视,电影,美女直播,迅雷资源↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  软件世界网 --