不管是我们使用 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 的实现机制:
- document.readyState 表示文档当前的加载状态,因此可以使用
document.readyState==complete
来判断 DOM 是否加载完成,但是低版本的 firefox 不支持此属性:
- UNINITIALIZED:XML 对象被产生,但没有任何文件被加载。
- loading:加载程序进行中,但文件尚未开始解析。
- loaded:部分的文件已经加载且进行解析,但对象模型尚未生效。
- interactive:仅对已加载的部分文件有效,在此情况下,对象模型是有效但只读的。
- complete:文件已完全加载,代表加载成功。
-
DOMContentLoaded 是当所有 DOM 结构解析完,而不用等待其他的资源(如图片,PDF 等)加载完成,就可以触发这个事件;所有的高级浏览器都支持此事件,只有低版本的 IE 不支持此事件;
-
ie6~8 可以使用 document.onreadystatechange 事件监听 document.readyState 状态是否等于 complete 来判断 DOM 是否加载完毕,如果页面中嵌有 iframe 的话,ie6~8 的 document.readyState 会等到 iframe 中的所有资源加载完才会变成 complete,此时 iframe 变成了耗时大户。但是经过测试,即使页面中没有 iframe,当 readyState 等于 complete 时,实际触发的是 onload 事件而不是 DOMContentLoaded 事件,对这点表示惊奇。
-
还好 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);
}
}
看着写了挺多,其实还有很多没有说到点子上,只是写了一些自己的理解罢了!!