jQuery的ready函数源码解读

若是你对$(document).ready()的理解也仅限于在DOM Tree绘制完毕后触发,那么,你也应该好好研究下ready的工做原理,由于,TST的面试官问过我这个问题。。。javascript

1、关于jQuery

jQuery是一个伟大的脚本库,由John Resig在 2006年1月的BarCamp NYC上释出第一个版本。你能够在 http://jquery.com/ 下载到最新版本。这里以jQuery1.8.3为例分析。
学习jQuery有许多途径,咱们今天从jQueryready函数开始。本例中的代码都来自于jQuery脚本库。
若是你使用过jQuery,就必然使用过ready函数,它用来注册当页面准备好以后能够执行的函数。
问题来啦,咱们的页面何时准备好了呢?css

2、onload事件

最基本的处理方式就是页面的onload事件,咱们在处理这个事件的时候,能够有多种方式,便可以经过HTML方式,直接写在body元素的开始标记中,也可使用事件注册的方式来使用,这又能够分为DOM0方式和DOM2方式。再考虑到浏览器的兼容性,使用DOM2方式写出来,以下所示。html

if (document.addEventListener) {
    // A fallback to window.onload, that will always work
    window.addEventListener("load", jQuery.ready, false);

    // If IE event model is used
} else {
    // A fallback to window.onload, that will always work
    window.attachEvent("onload", jQuery.ready);
}

3、DOMContentLoaded事件

不过onload事件要等到全部页面元素加载完成才会触发,包括页面上的图片等等。若是网页上有大量的图片,效果可想而知,用户可能在没有看到图片的时候,就已经开始操做页面了,而这时咱们的页面尚未初始化,事件尚未注册上,这岂不是太晚了!java

除了你们熟知的onload事件以外, 与DOM中的onload事件相近的,咱们还有 DOMContentLoaded事件能够考虑, 基于标准的浏览器支持这个事件, 当全部DOM 解析完之后会触发这个事件。jquery

这样,对于基于标准的浏览器来讲,咱们还能够注册这个事件的处理。这样,咱们可能更早地捕获到加载完成的事件。面试

if (document.addEventListener) {
    // Use the handy event callback
    document.addEventListener("DOMContentLoaded", DOMContentLoaded, false);

    // A fallback to window.onload, that will always work
    window.addEventListener("load", jQuery.ready, false);

}

4、onreadystatechange事件

不标准的浏览器怎么办呢?api

若是浏览器存在 document.onreadystatechange 事件,当该事件触发时,若是 document.readyState=complete 的时候,可视为 DOM 树已经载入。promise

不过,这个事件不太可靠,好比当页面中存在图片的时候,可能反而在 onload 事件以后才能触发,换言之,它只能正确地执行于页面不包含二进制资源或很是少或者被缓存时做为一个备选吧。浏览器

if (document.addEventListener) {
    // Use the handy event callback
    document.addEventListener("DOMContentLoaded", DOMContentLoaded, false);

    // A fallback to window.onload, that will always work
    window.addEventListener("load", jQuery.ready, false);

    // If IE event model is used
} else {
    // Ensure firing before onload, maybe late but safe also for iframes
    document.attachEvent("onreadystatechange", DOMContentLoaded);

    // A fallback to window.onload, that will always work
    window.attachEvent("onload", jQuery.ready);
}

DOMContentLoaded 函数在作什么呢?最终仍是要调用 jQuery.ready 函数。缓存

DOMContentLoaded = function() {
    if ( document.addEventListener ) {
        document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
        jQuery.ready();
    } else if ( document.readyState === "complete" ) {
        // we're here because readyState === "complete" in oldIE
        // which is good enough for us to call the dom ready!
        document.detachEvent( "onreadystatechange", DOMContentLoaded );
        jQuery.ready();
    }
}

5、doScroll检测法

MSDN 关于 JScript 的一个方法有段不起眼的话,当页面 DOM 未加载完成时,调用 doScroll 方法时,会产生异常。那么咱们反过来用,若是不异常,那么就是页面DOM加载完毕了!

Diego Perini 在 2007 年的时候,报告了一种检测 IE 是否加载完成的方式,使用 doScroll 方法调用。详细的说明见这里
原理是对于 IE 在非 iframe 内时,只有不断地经过可否执行 doScroll 判断 DOM 是否加载完毕。在本例中每间隔 50 毫秒尝试去执行 doScroll,注意,因为页面没有加载完成的时候,调用 doScroll 会致使异常,因此使用了 try -catch 来捕获异常。

(function doScrollCheck() {
    if (!jQuery.isReady) {

        try {
            // Use the trick by Diego Perini
            // http://javascript.nwbox.com/IEContentLoaded/
            top.doScroll("left");
        } catch (e) {
            return setTimeout(doScrollCheck, 50);
        }

        // and execute any waiting functions
        jQuery.ready();
    }
})();

6、document.readyState状态

若是咱们注册 ready 函数的时间点太晚了,页面已经加载完成以后,咱们才注册本身的 ready 函数,那就用不着上面的层层检查了,直接看看当前页面的 readyState 就能够了,若是已是 complete ,那就能够直接执行咱们准备注册的 ready 函数了。不过 ChrisS 报告了一个很特别的错误状况,咱们须要延迟一下执行。

setTimeout 常常被用来作网页上的定时器,容许为它指定一个毫秒数做为间隔执行的时间。当被启动的程序须要在很是短的时间内运行,咱们就会给她指定一个很小的时间数,或者须要立刻执行的话,咱们甚至把这个毫秒数设置为0,但事实上,setTimeout有一个最小执行时间,当指定的时间小于该时间时,浏览器会用最小容许的时间做为setTimeout的时间间隔,也就是说即便咱们把setTimeout的毫秒数设置为0,被调用的程序也没有立刻启动。

这个最小的时间间隔是多少呢?这和浏览器及操做系统有关。在John Resig的新书《Javascript忍者的秘密》一书中提到

Browsers all have a 10ms minimum delay on OSX and a(approximately) 15ms delay on Windows.(在苹果机上的最小时间间隔是10毫秒,在Windows系统上的最小时间间隔大约是15毫秒)

另外,MDC中关于setTimeout的介绍中也提到,Firefox中定义的最小时间间隔(DOM_MIN_TIMEOUT_VALUE)是10毫秒,HTML5定义的最小时间间隔是4毫秒。既然规范都是这样写的,那看来使用setTimeout是没办法再把这个最小时间间隔缩短了。

这样,经过设置为 1, 咱们可让程序在浏览器支持的最小时间间隔以后执行了。

// Catch cases where $(document).ready() is called after the browser event has already occurred.
// we once tried to use readyState "interactive" here, but it caused issues like the one
// discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
if (document.readyState === "complete") {
    // 延迟 1 毫秒以后,执行 ready 函数
    setTimeout(jQuery.ready, 1);
}

7、完整代码

jQuery.ready.promise = function( obj ) {
    if ( !readyList ) {

        readyList = jQuery.Deferred();

        // Catch cases where $(document).ready() is called after the browser event has already occurred.
        // we once tried to use readyState "interactive" here, but it caused issues like the one
        // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
        if ( document.readyState === "complete" ) {
            // Handle it asynchronously to allow scripts the opportunity to delay ready
            setTimeout( jQuery.ready, 1 );

        // Standards-based browsers support DOMContentLoaded
        } else if ( document.addEventListener ) {
            // Use the handy event callback
            document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );

            // A fallback to window.onload, that will always work
            window.addEventListener( "load", jQuery.ready, false );

        // If IE event model is used
        } else {
            // Ensure firing before onload, maybe late but safe also for iframes
            document.attachEvent( "onreadystatechange", DOMContentLoaded );

            // A fallback to window.onload, that will always work
            window.attachEvent( "onload", jQuery.ready );

            // 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 {
                            // Use the trick by Diego Perini
                            // http://javascript.nwbox.com/IEContentLoaded/
                            top.doScroll("left");
                        } catch(e) {
                            return setTimeout( doScrollCheck, 50 );
                        }

                        // and execute any waiting functions
                        jQuery.ready();
                    }
                })();
            }
        }
    }
    return readyList.promise( obj );
};

那么,又是谁来调用呢?固然是须要的时候,在咱们调用 ready 函数的时候,才须要注册这些判断页面是否彻底加载的处理,这段代码在 1.8.3 中位于代码的 #244 行,以下所示:

ready: function( fn ) {
    // Add the callback
    jQuery.ready.promise().done( fn );

    return this;
}

在页面上引用 jQuery 脚本库以后,执行了 jQuery 的初始化函数,初始化函数中建立了 ready 函数。咱们在经过 ready 函数注册事件处理以前,jQuery完成了页面检测代码的注册。这样。当页面彻底加载以后,咱们注册的函数就被调用了。

8、参考

1.jQuery 的 ready 函数是如何工做的?
2.jQuery ready函数实现原理
3.deferred.done()

相关文章
相关标签/搜索