JQuery源码分析(三)
jQuery中ready与load事件
jQuery有3种针对文档加载的方法
$(document).ready(function() { // ...代码... }) //document ready 简写 $(function() { // ...代码... }) $(document).load(function() { // ...代码... })
一个是ready一个是load,这两个到底有什么区别呢?
ready与load谁先执行:
大家在面试的过程中,经常会被问到一个问题:ready与load那一个先执行,那一个后执行?答案是ready先执行,load后执行。
DOM文档加载的步骤:
要想理解为什么ready先执行,load后执行就要先了解下DOM文档加载的步骤:
(1) 解析HTML结构。 (2) 加载外部脚本和样式表文件。 (3) 解析并执行脚本代码。 (4) 构造HTML DOM模型。//ready (5) 加载图片等外部文件。 (6) 页面加载完毕。//load
从上面的描述中大家应该已经理解了吧,ready在第(4)步完成之后就执行了,但是load要在第(6)步完成之后才执行。
结论:
ready与load的区别就在于资源文件的加载,ready构建了基本的DOM结构,所以对于代码来说应该越快加载越好。
在一个高速浏览的时代,没人愿意等待答案。假如一个网站页面加载超过4秒,不好意思,你1/4的用户将面临着流失,
所以对于框架来说用户体验是至关重要的,我们应该越早处理DOM越好,我们不需要等到图片资源都加载后才去处理框架的加载,图片资源过多load事件就会迟迟不会触发。
我们看看jQuery是如何处理文档加载时机的问题:
jQuery.ready.promise = function( obj ) { if ( !readyList ) { readyList = jQuery.Deferred(); if ( document.readyState === "complete" ) { // Handle it asynchronously to allow scripts the opportunity to delay ready setTimeout( jQuery.ready ); } else { document.addEventListener( "DOMContentLoaded", completed, false ); window.addEventListener( "load", completed, false ); } } return readyList.promise( obj ); };
jQuery的ready是通过promise给包装过的,这也是jQuery擅长的手法,统一了回调体系,以后我们会重点谈到。
可见jQuery兼容的具体策略:针对高级的浏览器,我们当前很乐意用DOMContentLoaded事件了,省时省力。
那么旧的IE如何处理呢?
继续看jQuery的方案:
// Ensure firing before onload, maybe late but safe also for iframes 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 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(); } })(); }
如果浏览器存在 document.onreadystatechange 事件,当该事件触发时,如果 document.readyState=complete 的时候,
可视为 DOM 树已经载入。不过,这个事件不太可靠,比如当页面中存在图片的时候,可能反而在 onload 事件之后才能触发,
换言之,它只能正确地执行于页面不包含二进制资源或非常少或者被缓存时作为一个备选吧。
针对IE的加载检测
Diego Perini 在 2007 年的时候,报告了一种检测 IE 是否加载完成的方式,使用 doScroll 方法调用,详情可见http://javascript.nwbox.com/IEContentLoaded/。
原理就是对于 IE 在非 iframe 内时,只有不断地通过能否执行 doScroll 判断 DOM 是否加载完毕。
在上述中间隔 50 毫秒尝试去执行 doScroll,注意,由于页面没有加载完成的时候,调用 doScroll 会导致异常,所以使用了 try -catch 来捕获异常。
结论:所以总的来说当页面 DOM 未加载完成时,调用 doScroll 方法时,会产生异常。那么我们反过来用,如果不异常,那么就是页面DOM加载完毕了。
这都是我们在第一时间内处理ready加载的问题,如果ready在页面加载完毕后呢?
jQuery就必须针对这样的情况跳过绑定了:
if ( document.readyState === "complete" ) { // Handle it asynchronously to allow scripts the opportunity to delay ready setTimeout( jQuery.ready ); }
直接通过查看readyState的状态来确定页面的加载是否完成了。这里会给一个定时器的最小时间后去执行,主要保证执行的正确。
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <script src="http://img.mukewang.com/down/540812440001e40e00000000.js" type="text/javascript"></script> <script src="http://img.mukewang.com/down/541f6ff70001a0a500000000.js" type="text/javascript"></script> <title>dom加载</title> </head> <body> <script type="text/javascript"> show('观察脚本加载的顺序') document.addEventListener("DOMContentLoaded", function() { show('DOMContentLoaded回调') }, false); window.addEventListener("load", function() { show('load事件回调') }, false); show('脚本解析一') //测试加载 $(function(){ show('脚本解析二') }) show('脚本解析三') </script> </head> </body> </html>