A-A+

jQuery实现domReady和onload判断及兼容的方法

2016年01月07日 编程技术 暂无评论

要了解 jQuery 对 domReady 和 load 的区别,就是jQuery对于兼容的 domReady 是如何实现的而已。因为 onload 是各个浏览器已经实现的事件,而 DOMContentLoaded 未被低版本IE实现。

对于 jQuery 使用中 $( function(){} ) 的形式我们都不陌生,将事件函数绑定在 dom 加载完毕的时刻,相对于 window.onload 来讲可以避免一些图片,动态脚本,以及一些外部资源等加载对于当前代码执行带来的较大的延时影响,因为 window.onload 是需要等当前页面需要加载的资源全部加载结束后才触发执行,包括 onload 触发之前执行的动态加载。

对于标准浏览器,具有 DOMContentLoaded 事件来作为 dom 加载完毕后触发的事件,当然准确点说的话是 dom 和 非动态加载的JS 加载完毕时刻触发(关于触发时机可以看这里 JS、CSS以及img对DOMContentLoaded 事件的影响)。

当然低版本的IE(IE6 ~ IE8)不支持 DOMContentLoaded 事件,那么也需要使用一些其他的方法来进行模拟 DOMContentLoaded 时刻触发。当前主流的低版本IE模拟方法就是使用 doScroll 进行循环判断,对于 JScript ,若在 dom 未加载完毕时刻调用 doScroll 则会报错,借此使用 try catch 进行轮询判定。

那么,要了解 jQuery 对 domReady 和 load 的区分,其实就是说 jQuery 对于兼用的 domReady 是如何实现的而已,因为 onload 是各个浏览器已经实现的事件,而 DOMContentLoaded 未被低版本IE实现。然后本次使用的 jQuery 版本为 1.11,毕竟还要包括以下低版本IE,所以不使用 2.*的版本,但其实质是一样的,只不过 2.*版本中就舍弃使用 doScroll 进行循环判定放弃低版本IE而已。那么,要了解本质,最好的办法还是看 jQuery 源码。

  1. // DOM ready 的 一个 jQuery deferred   
  2. // 也作为 绑定初始化的判定标签  
  3. var readyList;  
  4.   
  5. // 调用绑定才进行 绑定初始化  
  6. jQuery.fn.ready = function( fn ) {  
  7.     // 未初始化过的才进行初始化绑定  
  8.     jQuery.ready.promise().done( fn );  
  9.     return this;  
  10. };  
  11.   
  12. jQuery.extend({  
  13.     // DOM ready 是否触发过标签   
  14.     isReady: false,  
  15.     readyWait: 1,  
  16.   
  17.     holdReady: function( hold ) {  
  18.         if ( hold ) {  
  19.             jQuery.readyWait++;  
  20.         } else {  
  21.             jQuery.ready( true );  
  22.         }  
  23.     },  
  24.   
  25.     // DOM ready 时刻的事件句柄  
  26.     ready: function( wait ) {  
  27.   
  28.         if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {  
  29.             return;  
  30.         }  
  31.   
  32.         // IE的小BUG 确保 body 已经存在  
  33.         if ( !document.body ) {  
  34.             return setTimeout( jQuery.ready );  
  35.         }  
  36.   
  37.         // 切换标签状态  
  38.         jQuery.isReady = true;  
  39.   
  40.         // 是否需要等待的标签  
  41.         if ( wait !== true && --jQuery.readyWait > 0 ) {  
  42.             return;  
  43.         }  
  44.   
  45.         // 执行以 jquery 形式的 ready 绑定  
  46.         readyList.resolveWith( document, [ jQuery ] );  
  47.   
  48.         // 在次触发其他形式的绑定,并且及时注销  
  49.         if ( jQuery.fn.triggerHandler ) {  
  50.             jQuery( document ).triggerHandler( "ready" );  
  51.             jQuery( document ).off( "ready" );  
  52.         }  
  53.     }  
  54. });  

扩展的 isReady 是用来控制 ready 的执行次数的,readyWait 和 holdReady 在此不在此文解释范围,其中的 ready 方法 就是核心了,为 jQuery 判定 domReady 时所触发的方法,又结合了 defferred 的 resolveWith 逐一执行存储在 cache 中事件句柄数组中的事件句柄。那么接下来就是 domReady 的重头戏,判定 domReady 时刻。

  1. // 移除所有有关事件句柄 compeleted 的绑定事件  
  2. function detach() {   
  3.     if ( document.addEventListener ) {   
  4.         document.removeEventListener( "DOMContentLoaded", completed, false );   
  5.         window.removeEventListener( "load", completed, false );   
  6.     } else {   
  7.         document.detachEvent( "onreadystatechange", completed );   
  8.         window.detachEvent( "onload", completed );   
  9.     }   
  10. }   
  11.   
  12. // 对于已经过了 domReady 时间点  
  13. function completed() {   
  14.     // 标准浏览器 或者 为 IE下的load事件 或者 ready 状态为 complete(IE)  
  15.     if ( document.addEventListener || event.type === "load" || document.readyState === "complete" ) {   
  16.         detach();   
  17.         jQuery.ready();   
  18.     }   
  19. }   
  20.   
  21. jQuery.ready.promise = function( obj ) {   
  22.     if ( !readyList ) {   
  23.         // 初始化 readyList  
  24.         readyList = jQuery.Deferred();   
  25.   
  26.         // 当 $(document).ready() 早在 ready 事件过后被调用的时候,直接触发。   
  27.         if ( document.readyState === "complete" ) {   
  28.             // 异步方式执行 类似的模拟事件触发形式  
  29.             setTimeout( jQuery.ready );   
  30.   
  31.             // DOMContentLoaded 事件绑定  
  32.         } else if ( document.addEventListener ) {   
  33.             document.addEventListener( "DOMContentLoaded", completed, false );   
  34.             window.addEventListener( "load", completed, false );   
  35.   
  36.         // 这边则是低版本IE形式  
  37.         } else {   
  38.             document.attachEvent( "onreadystatechange", completed );   
  39.             window.attachEvent( "onload", completed );   
  40.   
  41.             // IE的 doScroll 方式检测  
  42.             var top = false;   
  43.   
  44.             try {   
  45.                 top = window.frameElement == null && document.documentElement;   
  46.             } catch(e) {}   
  47.   
  48.             if ( top && top.doScroll ) {   
  49.                 (function doScrollCheck() {   
  50.                     if ( !jQuery.isReady ) {   
  51.                         try {   
  52.                             top.doScroll("left");   
  53.                         } catch(e) {   
  54.                             // 当try中有错误 直接下次延迟循环检测  
  55.                             return setTimeout( doScrollCheck, 50 );   
  56.                         }   
  57.   
  58.                         detach();   
  59.                         jQuery.ready();   
  60.                     }   
  61.                 })();   
  62.             }   
  63.         }   
  64.     }   
  65.     return readyList.promise( obj );   
  66. };  

其实对于 jQuery 这种形式来说,有点类似于 宁可错杀一千,也不放过一个,在任何状态下尽可能的多监听些事件进行判断,在 DOMContentLoaded,load,readystatechange 以及 doScroll 循环等, 反正有 jQuery.isReady 进行把关么,怕什么。对于该流程进一步了解还需要去了解一下 jquery 的 promise。关于 jQuery 的 callback, defferred的作用和源码。为什么要返回 readyList.promise( obj ),自己思考下吧!

标签:

给我留言