【jQuery源码】DOM Ready

一直以来,各种JS最佳实践都会告诉我们,将JS放在HTML的最后,即</body>之前,理由就是:JS会阻塞下载,而且,在JS中很有可能有对DOM的操作,放在HTML的最后,可以尽可能的保证JS的执行在DOM加载完成之后。而如果放在onload事件中执行,如果页面有很多图像,那么页面的onload事件要过很久才会触发,因此DOM Ready事件就是最好的执行JS的时间了。

所以,如果有个DOM Ready事件就好了,虽然现代浏览器已经支持DOMContentLoaded事件,但是我们还是得处理那些老旧的浏览器,于是DOM Ready事件就成了各个JS框架的必备功能啦。

先来看看jQuery DOM Ready事件的用法吧,jQuery的DOM Ready事件用法很简单,大家都用过,下面的三种语法都是可用的:

  • $( document ).ready( handler )
  • $().ready( handler )(不推荐)
  • $( handler )

还有一种方式:$(document).on(‘ready‘, handler);,已经在1.8版本里被废弃了,但是你还可以这么用。这种方式和ready方法的作用一样,但是如果ready事件已经被触发过了,这种方式绑定的handler将不会被执行,而且用这种方式绑定的handler会在以上三种方法绑定的handler被执行后再执行。

一、DOM Ready检测

下面,我们按照jQuery的思路,一步步的看一下jQuery如何处理DOM Ready事件的。

1. 标准浏览器

对于标准浏览器,我们可以添加DOMContentLoaded事件监听器:

if ( document.addEventListener ) {
    document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
}
2. 非标准浏览器

而对于非标准浏览器,我们可以监听document.onreadystatechange事件,如果document.readyState == ‘complete’时,可以认为DOM结构已经加载完成。

if ( document.addEventListener ) {
    document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );

    // If IE event model is used
} else {
    document.attachEvent( "onreadystatechange", DOMContentLoaded );
}

在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();
    }
};
3. 安全起见

上面的检测方式应该够了,但是处于安全的考虑,万一前面的判断失效了怎么办。这时候,onload事件虽然可能触发的比较晚,但是它能可靠的被触发,因此在判断中添加了对onload事件的监听:

if ( document.addEventListener ) {
    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 {
    document.attachEvent( "onreadystatechange", DOMContentLoaded );

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

这里不用担心jQuery.ready()会被多次触发,因为在jQuery.ready()中有检测是否被触发过了。

4. 还是IE

在IE中,onreadystatechange事件对于iframe的来说可能会有延迟,但是却足够安全。但这个事件对于非iframe有时会不太可靠,特别是在页面中图片资源比较多的时候,可能反而在onload事件触发之后才能触发,因此对于这种情况,需要做进一步的检测:

if ( document.addEventListener ) {
    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 {
    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) {}

    // 如果是非iframe的情况,并且支持doScroll方法
    if ( top && top.doScroll ) {
        // 在这个自执行函数中,调用top.doScroll方法,若报错,则延时50ms再检测;若不报错,则表示DOM Ready,可以执行jQuery.ready()方法了
        (function doScrollCheck() {
            if ( !jQuery.isReady ) {
                try {
                    top.doScroll("left");
                } catch(e) {
                    return setTimeout( doScrollCheck, 50 );
                }
                // and execute any waiting functions
                jQuery.ready();
            }
        })();
    }
}

是的,JS就是这么一门神(dan)奇(teng)的语言,每个浏览器的实现可能不尽相同,所以就出现了这种对一个事件的监听要做如此多的兼容考虑的情况。

5. Ready太晚了

还有一种情况是,当我们调用$(document).ready()的时候,DOM结构已经加载完成了,这时候可以直接调用jQuery.ready,但是因为IE的问题,需要延迟调用,所以有下面的代码,至于为什么要这么用,我也没看明白:

if ( document.readyState === "complete" ) {
    // Handle it asynchronously to allow scripts the opportunity to delay ready
    setTimeout( jQuery.ready, 1 );
} else if ( document.addEventListener ) {
    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 {
    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) {}

    // 如果是非iframe的情况,并且支持doScroll方法
    if ( top && top.doScroll ) {
        // 在这个自执行函数中,调用top.doScroll方法,若报错,则延时50ms再检测;若不报错,则表示DOM Ready,可以执行jQuery.ready()方法了
        (function doScrollCheck() {
            if ( !jQuery.isReady ) {
                try {
                    top.doScroll("left");
                } catch(e) {
                    return setTimeout( doScrollCheck, 50 );
                }
                // and execute any waiting functions
                jQuery.ready();
            }
        })();
    }
}

到这里,jQuery对DOM Ready的检测就完成了,那么$(document).ready()是怎么工作的呢?

二、DOM Ready全过程

这里,我们按代码执行的顺序,来理解jQuery的DOM Ready的全部代码,看jQuery是如何一步步处理DOM Ready事件的。为了阅读的方便,我将代码做了删减,并对代码顺序做了调整,大家可以按从1到7的顺序看代码。这里不对Deferred对象做解释,因为我也还没有看,只是记住他的作用是维护一系列条件触发的回调函数。

jQuery.fn = jQuery.prototype = {
    init: function( selector, context, rootjQuery ) {
        if ( jQuery.isFunction( selector ) ) {
            // 当调用$(callback); 时,会调用$(document).ready(callback);      Ⅰ
            return rootjQuery.ready( selector );
        }
    },

    ready: function( fn ) {
        // Add the callback
        // 调用jQuery.ready.promise方法,返回一个Deferred对象 readyList,
        // 然后将fn加入到readyList的成功的回调列表中。
        // 如果readyList已存在,则直接返回readyList,然后直接调用这个回调函数fn
        jQuery.ready.promise()                               Ⅱ
            .done( fn );                              Ⅳ

        return this;
    }
};

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

        readyList = jQuery.Deferred();

        // IE的bug
        if ( document.readyState === "complete" ) {
            setTimeout( jQuery.ready, 1 );

            // 给标准浏览器绑定DOMContentLoaded事件以触发DOMContentLoaded方法
            // 同时,绑定window.onload事件作为备用方案
        } else if ( document.addEventListener ) {
            document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
            window.addEventListener( "load", jQuery.ready, false );

        // 给IE浏览器绑定onreadystatechange事件,在DOMContentLoaded中判断
        // readyState是否为complete
        } else {
            document.attachEvent( "onreadystatechange", DOMContentLoaded );
            window.attachEvent( "onload", jQuery.ready );

            // 对IE做进一步的兼容性检测
            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();
                    }
                })();
            }
        }
    }
    // 返回readyList
    return readyList.promise( obj );                 Ⅲ
};

// 接下来就是等待DOMContentLoaded/readyState===‘complete‘事件的发生了
// 移除DOMReady事件监听,执行jQuery.ready()
    DOMContentLoaded = function() {                  Ⅴ
        // 标准浏览器
        if ( document.addEventListener ) {
            document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
            jQuery.ready();
        // IE 浏览器
        } else if ( document.readyState === "complete" ) {
            document.detachEvent( "onreadystatechange", DOMContentLoaded );
            jQuery.ready();
        }
    };

jQuery.extend({
    // DOM Ready标志位,DOM Ready触发后置为true
    isReady: false,

    // DOM Ready后运行
    ready: function( ) {

        // 如果已经Ready,则直接返回
        if ( jQuery.isReady ) {
            return;
        }

        // 兼容IE
        if ( !document.body ) {
            return setTimeout( jQuery.ready, 1 );
        }

        // 将isReady置为true
        jQuery.isReady = true;              Ⅵ

        // 这里是真正触发我们传入的回调的地方!!!!
        // 传入上下文和回调函数的参数列表
        // 注意啦,上下文是document,也就是说我们在`$(document).ready(fn);`里的fn中的this是`document`,不是`window`
        // 参数列表传入了jQuery,意味着即使我们调用了`$.noConflict()`将$让渡出去了,我们仍然能在fn中定义第一个参数为$,代表jQuery
        readyList.resolveWith( document, [ jQuery ] );              Ⅶ

        // 这段代码是为了触发这种方式添加的DOM Ready回调: $(document).on(‘ready‘ , fn);
        // 这种方式在1.8被废弃,但是没有被移除
        // 你可以看到这种方式添加的DOM Ready回调是最后被执行的
        if ( jQuery.fn.trigger ) {
            jQuery( document ).trigger("ready").off("ready");
        }
    }
});

看完代码,让我们再来看一下jQuery的思路:

  • Ⅰ. 调用rootjQuery的ready事件
  • Ⅱ. 调用jQuery.ready.promose(),若readyList为空,将readyList赋值为一个Deferred对象;否则将回调函数添加到readyList成功回调列表中,然后跳转到第7步
  • Ⅲ. 在jQuery.promise()中为DOM Ready事件添加监听器
  • Ⅳ. 将回调函数fn添加到readyList的成功回调列表中
  • Ⅴ. 当DOM Ready事件发生时,移除监听器,执行jQuery.ready()
  • Ⅵ. 在jQuery.ready()中将isReady置为true
  • Ⅶ. 执行readyList的成功回调列表中的方法
时间: 2024-11-05 12:28:54

【jQuery源码】DOM Ready的相关文章

jQuery源码dom ready分析

一.前言 在平时开发web项目时,我们使用jquery框架时,可能经常这样来使用$(document).ready(fn),$(function(){}),这样使用的原因是在浏览器把DOM树渲染好之前,javascript是无法操作没渲染好的DOM节点. 其实除了$(document).ready(fn),$(function(){})写法外,还有两种让dom渲染完之后执行js的写法: $(document).on('ready', fn2) //通过on事件绑定函数,通过trigger触发也可

jquery 源码学习(三)

jQuery源码分析-03构造jQuery对象-源码结构和核心函数,需要的朋友可以参考下. 作者:nuysoft/高云 QQ:47214707 EMail:[email protected] 毕竟是边读边写,不对的地方请告诉我,多多交流共同进步.本章还未写完,完了会提交PDF. 前记: 想系统的好好写写,但是会先从感兴趣的部分开始. 近期有读者把PDF传到了百度文库上,首先感谢转载和传播,但是据为已有并设置了挺高的财富值才能下载就不好了,以后我整理好了会传到文库上.请体谅一下. 3. 构造jQu

Jquery源码分析与简单模拟实现

前言 最近学习了一下jQuery源码,顺便总结一下,版本:v2.0.3 主要是通过简单模拟实现jQuery的封装/调用.选择器.类级别扩展等.加深对js/Jquery的理解. 正文 先来说问题: 1.jQuery为什么能使用$的方式调用,$是什么.$()又是什么.链式调用如何实现的 2.jQuery的类级别的扩展内部是怎样实现的,方法级别的扩展有是怎样实现的,$.fn又是什么 3.jQuery选择器是如何执行的,又是如何将结果包装并返回的 带着这些问题,我们进行jquery的模拟实现,文章下方有

Jquery源码分析

1.概述 jQuery是一个非常优秀的Js库,与prototype,YUI,Mootools等众多的Js类库相比,它剑走偏锋,从web开发最实用的角度出发,抛除了一些中看但不实用的东西,为开发者提供一个短小精悍的类库.由于其个短小精悍,使用简单方便,性能相对高效.众多的开发者都选择Jquery来进行辅助的web开发. 在使用jquery时开发,我们也会时常碰到许多的问题,但是jquery的代码很晦涩,难起看懂,当开发时出现了问题,看不懂源码,不知道如何去排错. John Resig,Jquery

jquery源码分析(二)——结构

再来复习下整体架构: jQuery源码分析(基于 jQuery 1.11 版本,共计8829行源码) (21,94)                定义了一些变量和函数jQuery=function(){} (96,280)        给jQuery添加一些方法和属性,jQuery.fn=jQuery.prototype(285,347)        extend:        jQuery的一些继承方法        更容易进行后续的扩展                       

jQuery源码学习2——初始化篇

这一篇主要总结一下jQuery这个js在引入的时候做的一些初始化工作 第一句window.undefined=window.undefined; 是为了兼容低版本的IE而写的 因为在低版本的IE中undefined不是window对象下的属性 因此window.undefined就是undefined 根据=运算符右结核性的特征,=右边的window.undefined就是undefined 既然window没有undefined属性 因此左边其实可以理解为在window下面扩展一个undefi

jquery 源码学习(四)构造jQuery对象-工具函数

jQuery源码分析-03构造jQuery对象-工具函数,需要的朋友可以参考下. 作者:nuysoft/高云 QQ:47214707 EMail:[email protected] 声明:本文为原创文章,如需转载,请注明来源并保留原文链接. 读读写写,不对的地方请告诉我,多多交流共同进步,本章的的PDF等本章写完了发布. jQuery源码分析系列的目录请查看 http://nuysoft.iteye.com/blog/1177451,想系统的好好写写,目前还是从我感兴趣的部分开始,如果大家有对哪

二.jQuery源码解析之构建jQuery之构建函数jQuery的7种用法

一:$(selectorStr[,限制范围]),接受一个选择器(符合jQuery规范的字符串),返回一个jQuery对象;二:$(htmlStr[,文档对象]),$(html[,json对象])传入html字符串,创建一个新的dom元素 三:$(dom元素),$(dom元素集合)将dom元素转换成jQuery对象.四:$(自定义对象)封装普通对象为jQuery对象.五:$(回调函数)绑定ready事件监听函数,当Dom加载完成时执行.六:$(jQuery对象)接受一个jQuery对象,返回一个j

阅读jQuery源码的18个惊喜

注释:本文使用$.fn.method指代调用一系列选中的元素的方法.例如,$.fn.addClass,指代$('div').addClass(‘blue’) 或 $('a.active’).addClass(‘in-use’)这些用法.$.fn指代jQuery对象. 1.Sizzle’s weight:Sizzle 是jQuery基于CSS选择器的DOM查找引擎.它可以将$(‘div.active’)转换成一个可操作的元素数组.Sizzle是jQuery很大的一个组成部分,但是它的规模之大的确令

jQuery源码06-jQuery = function(){};给JQ对象,添加一些方法和属性,extend : JQ的继承方法,jQuery.extend()

/*! * Includes Sizzle.js 选择器,独立的库 * http://sizzlejs.com/ */ (function( window, undefined ) { //"use strict"; var // rootjQuery = jQuery(document) = $();压缩有用 rootjQuery, // dom是否加载完 readyList, // core_strundefined == 'undefined' core_strundefined