Wenzi

简析jQuery中的ready事件

蚊子前端博客
发布于 2015/11/17 07:00
在我们使用jQuery时,通常都会用到ready事件,本篇主要讲解下ready事件的内部实现

不管是我们使用 jQuery 还是其他任何的库,我们大部分都会使用到 ready 的事件,因为很多的代码只能在 DOM 结构加载完成后才能执行,否则就会报错。因此都会封装跨浏览器兼容性的 ready 事件,不过本篇文章主要是讲解 jQuery 中的 ready 事件。

1. 为什么要使用 ready #

上面已经说了,“有些代码只能在 DOM 结构加载完成后才能执行”,不过有的人可能不是太理解,这里我们来举一个简单的例子

<script>
  var time = document.getElementById('time');
  time.innerHTML = Date.now();
</script>
<body>
  <div id="time"></div>
</body>

我们在页面中运行上面的代码,控制台就报错:Uncaught TypeError: Cannot set property 'innerHTML' of null,意思就是不能给不存在的对象设置 innerHTML 属性。这是因为当代码执行到document.getElementById('time')时,id 为 time 的 DOM 元素还没加载,找不到 time 这个元素,代码就报错了;正确的方式就是把 js 代码放到 body 的后面,这样就能保证所有的 DOM 元素都执行完后才执行 js 代码。

如果只是特别单纯简单的 js 代码,从上往下执行的那种,直接放在 body 的后面,是没必要使用 ready 的;但是,更多的情况,是没有那么多简单的 js 代码的,比如有的时候需要把 js 代码放在前面,有时需要延迟执行一些 js 代码,等等,这些就需要用到 ready 事件了。

2. ready 的书写方式 #

jQuery 中的 ready 事件通常有两种书写方式,效果都是一样的:

简写方式:

$(function () {
  // ready
});

完整的方式:

// 给document元素绑定ready事件
$(document).ready(function () {});

其实在 jQuery 的内部,简写的方式最终也是通过调用下面的方式的。我们来看 jQuery 的源码里:

// HANDLE: $(function)
// Shortcut for document ready
if () {

} else if ( jQuery.isFunction( selector ) ) {
	return typeof rootjQuery.ready !== "undefined" ?
		rootjQuery.ready( selector ) :
		// Execute immediately if ready is not present
		selector( jQuery );
}

$(selector)里,若参数 selector 是 function 类型的,即

$(function () {});

function ready() {}
$(ready);

则调用rootjQuery.ready( selector ),而且:

rootjQuery = jQuery(document);

即:

jQuery(document).ready(function () {});

3. ready 的触发机制是什么 #

我们在上面已经了解了 jQuery 的 ready 的使用方法,在这一节我们来了解下 ready 的实现机制:

  1. document.readyState 表示文档当前的加载状态,因此可以使用document.readyState==complete来判断 DOM 是否加载完成,但是低版本的 firefox 不支持此属性:
  • UNINITIALIZED:XML 对象被产生,但没有任何文件被加载。
  • loading:加载程序进行中,但文件尚未开始解析。
  • loaded:部分的文件已经加载且进行解析,但对象模型尚未生效。
  • interactive:仅对已加载的部分文件有效,在此情况下,对象模型是有效但只读的。
  • complete:文件已完全加载,代表加载成功。
  1. DOMContentLoaded 是当所有 DOM 结构解析完,而不用等待其他的资源(如图片,PDF 等)加载完成,就可以触发这个事件;所有的高级浏览器都支持此事件,只有低版本的 IE 不支持此事件;

  2. ie6~8 可以使用 document.onreadystatechange 事件监听 document.readyState 状态是否等于 complete 来判断 DOM 是否加载完毕,如果页面中嵌有 iframe 的话,ie6~8 的 document.readyState 会等到 iframe 中的所有资源加载完才会变成 complete,此时 iframe 变成了耗时大户。但是经过测试,即使页面中没有 iframe,当 readyState 等于 complete 时,实际触发的是 onload 事件而不是 DOMContentLoaded 事件,对这点表示惊奇。

  3. 还好 ie 有个特有的 doScroll 方法,当页面 DOM 未加载完成时,调用 doScroll 方法时,就会报错,反过来,只要一直间隔调用 doScroll 直到不报错,那就表示页面 DOM 加载完毕了,不管图片和 iframe 中的内容是否加载完毕,此法都有效。

当我们了解了上面的知识后,就容易理解 jQuery 的 ready 设计机制了。

// 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
// 曾经打算用document.readyState === "interactive"来判断DOM是否加载完成,但是会出现bug,因此使用complete
// 如果当前状态已经是complete里,就直接执行
if (document.readyState === 'complete') {
  // Handle it asynchronously to allow scripts the opportunity to delay ready
  // 异步执行ready方法,以保证脚本有机会延迟执行(holdReady)
  setTimeout(jQuery.ready);

  // Standards-based browsers support DOMContentLoaded
  // 支持addEventListener的浏览器都支持DOMContentLoaded,直接调用DOMContentLoaded即可
} else if (document.addEventListener) {
  // Use the handy event callback
  document.addEventListener('DOMContentLoaded', completed, false);

  // A fallback to window.onload, that will always work
  // 有的浏览器会缓存load方法,下一次加载时load有可能优先于DOMContentLoaded触发
  // 因此两个都写上,哪个先触发就走哪个
  // 但是两个都写上,就不怕触发两次么,先把这段代码看完,然后看第4节
  window.addEventListener('load', completed, false);

  // If IE event model is used
  // 如果当前是IE浏览器
} else {
  // Ensure firing before onload, maybe late but safe also for iframes
  // 绑定onreadystatechange以监听readyState的变化
  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
  // 如果当前页面中没有frame,则持续检查是否可以调用doScroll方法以监听document是否加载完成
  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);
        }

        // detach all dom ready events
        detach();

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

4. completed 方法 #

在上面的方法中,不论是触发 DOMContentLoaded,还是 load,还是 onreadystatechange,都会执行 completed,那这个方法里都干了些什么呢?我们来看看。

/**
 * The ready event handler and self cleanup method
 */
function completed() {
  // readyState === "complete" is good enough for us to call the dom ready in oldIE
  // 即使在老版IE下readyState === "complete"也足以说明DOM加载完成
  if (document.addEventListener || event.type === 'load' || document.readyState === 'complete') {
    detach(); // 执行detach函数
    jQuery.ready(); // 执行ready
  }
}

/**
 * Clean-up method for dom ready events
 */
// 从这个方法里可以看到,不论是先执行DOMContentLoaded还是先执行load,都会把两个事件同时解除掉,因此这两个事件最多只能触发一次;IE下同理
function detach() {
  if (document.addEventListener) {
    document.removeEventListener('DOMContentLoaded', completed, false);
    window.removeEventListener('load', completed, false);
  } else {
    document.detachEvent('onreadystatechange', completed);
    window.detachEvent('onload', completed);
  }
}

看着写了挺多,其实还有很多没有说到点子上,只是写了一些自己的理解罢了!!

标签:jqueryready
阅读(1213)
Simple Empty
No data