一直以来,各种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的成功回调列表中的方法

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

  1. jQuery源码dom ready分析

    一.前言 在平时开发web项目时,我们使用jquery框架时,可能经常这样来使用$(document).ready(fn),$(function(){}),这样使用的原因是在浏览器把DOM树渲染好之前 ...

  2. jquery源码 DOM加载

    jQuery版本:2.0.3 DOM加载有关的扩展 isReady:DOM是否加载完(内部使用) readyWait:等待多少文件的计数器(内部使用) holdReady():推迟DOM触发 read ...

  3. jQuery源码-dom操作之jQuery.fn.html

    写在前面 前面陆陆续续写了jQuery源码的一些分析,尽可能地想要cover里面的源码细节,结果导致进度有些缓慢.jQuery的源码本来就比较晦涩,里面还有很多为了解决兼容问题很引入的神代码,如果不g ...

  4. jQuery源码-dom操作之jQuery.fn.text

    写在前面 jQuery.fn.text在jQuery是个使用频率比较高的接口,它的作用无非是设置/获取dom节点的内容文本,下文会通过几个简单的例子来说明.text()接口的使用,以及最后会对源码进行 ...

  5. jQuery源码:从原理到实战

    jQuery源码:从原理到实战 jQuery选择器对象 $(".my-class"); document.querySelectorAll*".my-class" ...

  6. 阅读jQuery源码的18个惊喜

    注释:本文使用$.fn.method指代调用一系列选中的元素的方法.例如,$.fn.addClass,指代$('div').addClass(‘blue’) 或 $('a.active’).addCl ...

  7. 关于jQuery源码中(function(window,undefined){//dosomething()})(window)写法解释

    一.首先是最常见的闭包 (Closure) 范式自执行函数的写法,这里用匿名函数封装(构造块级作用域),避免了匿名函数内部的代码与外部之间发生冲突(如使用了相同的变量名). (function() { ...

  8. jQuery源码整体结构(源码2.0.3)

    拨开jQuery的面纱,最近了解了下jQuery源码整体框架.主要包括: (1)  jQuery 核心模块 (2)  sizzle 选择器引擎 (3)  Deferred 异步队列 (4)  Supp ...

  9. jQuery 源码基本框架

    抽丝剥茧, 7000+ 行的 jQuery 源码基本可以概括为以下的伪代码 (function (window, undefined) { //将 document 封装成 jQuery 对象并缓存 ...

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

    前言 最近学习了一下jQuery源码,顺便总结一下,版本:v2.0.3 主要是通过简单模拟实现jQuery的封装/调用.选择器.类级别扩展等.加深对js/Jquery的理解. 正文 先来说问题: 1. ...

随机推荐

  1. HDU 1513 && POJ 1159 Palindrome (DP+LCS+滚动数组)

    题意:给定一个字符串,让你把它变成回文串,求添加最少的字符数. 析:动态规划是很明显的,就是没有了现思路,还是问的别人才知道,哦,原来要么写,既然是回文串, 那么最后正反都得是一样的,所以我们就正反求 ...

  2. Android属性动画之ValueAnimator的介绍

    之前两篇博客,介绍的是ObjectAnimator作用与某一个控件的某一个属性.但我们的ValueAnimator它本身并不会作用与任何一个属性,它本身也不会提供任何一种动画.它简单的来说,就是一个数 ...

  3. MySQL性能调优与架构设计——第 15 章 可扩展性设计之Cache与Search的利用

    第 15 章 可扩展性设计之Cache与Search的利用 前言: 前面章节部分所分析的可扩展架构方案,基本上都是围绕在数据库自身来进行的,这样是否会使我们在寻求扩展性之路的思维受到“禁锢”,无法更为 ...

  4. What if you are involved in an automobile accident in the US

    What if you are involved in an automobile accident in the US With increasing Chinese tourists and vi ...

  5. java递归和反向递归

    1. 递归查询树tree结构有两种做法: 第一种,递归查询数据库结构, 第二种,一次性将数据库表中的所有数据查出来,然后再递归查出来的list集合, 第一种做法适合数据量较少的tree结构,因为要一直 ...

  6. 【WP8.1】页面的导航效果

    <Page.Transitions> <TransitionCollection> <NavigationThemeTransition> <CommonNa ...

  7. NETSDK1061错误解决

    NETSDK1061错误解决 在vs生成和运行都正常,发布的时候报错 .netcore控制台项目引用另一个类库 错误信息 NETSDK1061: 项目是使用 Microsoft.NETCore.App ...

  8. mvc 读写txt文档

    -----------------写入内容---------------- string userfile = "UserData.txt"; StreamWriter sw = ...

  9. Android学习之ViewPager

     1.定义 ViewPager是android扩展包v4包中的类,这个类可以让用户左右切换当前的view.其中,android.support.v4是谷歌公司为了解决当前版本碎片化的问题,从而提供的一 ...

  10. AOP面向切面的基石——动态代理(一)

    其实动态代理在Java里不是什么新技术了,早在java 1.2之后便通过 java.lang.reflect.InvocationHandler 加入了动态代理机制. 下面例子中,LancerEvol ...