jQuery源码分析-10事件处理-Event-DOM-ready

Js代码   收藏代码
  1. 作者:nuysoft/JS攻城师/高云 QQ:47214707 EMail:nuysoft@gmail.com    
  2. 声明:本文为原创文章,如需转载,请注明来源并保留原文链接。     
  3. 后文预告:封装事件对象 便捷接口解析  
  4.   
  5. 前记:  
  6. 这一章写的很用心,希望有所启发。因为排版的原因,阅读附件PDF更方便一些。  
  7.   
  8. jQuery源码分析系列大概已经写了60%,架构、构建jQuery对象、异步队列、浏览器测试、数据缓存、队列、事件模型、AJAX、动画、尺寸大小基本完结,这些比较难读也是我感兴趣的,剩余的部分有选择器、DOM遍历和操作、CSS和部分章节的补充,jQuery中的正则分析已有初稿但是内容太庞大还在拿捏怎么写合适,一些章节写的不是很满意有时间会做修改,详细的目录和计划可以去目录页参看 http://nuysoft.iteye.com/blog/1190542。  
  9.   
  10. JavaScript同其他语言一样,基础非常重要,仅仅会用框架和工具写一些WebApp是不能成为一名合格的JS工程师的,建议多读几次JavaScript权威指南,以我的经验与Java相比,JavaScript需要花费与Java相当的时间才能达到与Java相当的水平。  
  11.   
  12. jQuery完结之后计划写Java开源框架的源码解析,从Spring、Hibernate开始,有兴趣的同学可以一起研究。  
Js代码   收藏代码
  1. 10.8    DOM ready  
  2.   
  3. 初学jQuery时,很可能学到的第一个知识点就是.ready(),但是网络上流转的文章和API对此的解释或剖析,经常是模棱两可甚至是不准确或错误的,本节将详细的阐述.ready()的用法和原理,并纠正这些误区。  
  4.   
  5. 10.8.1  如何使用.ready()  
  6.   
  7. .ready() 指定一个事件句柄,当DOM完全加载完成时执行  
  8.   
  9. 虽然JavaScript提供了load事件,当页面渲染完成之后会执行这个函数,在所以元素加载完成之前,这个函数不会被调用,例如图像。但是在大多数情况下,只要DOM结构加载完,脚本就可以尽快运行。传递给.ready()的事件句柄在DOM准备好后立即执行,因此通常情况下,最好把绑定事件句柄和其他jQuery代码都到这里来。但是当脚本依赖于CSS样式属性时,一定要在脚本之前引入外部样式或内嵌样式的元素。  
  10.   
  11. 如果代码依赖于需加载完的元素(例如,想获取一个图片的尺寸大小),应该用.load()事件代替,并把代码放到load事件句柄中。  
  12.   
  13. .ready()方法不同于<body οnlοad="">属性。如果必须用load事件,那么就不要使用.ready(),或用.load()代替,把load事件句柄绑定到window或其他的具体元素上,例如图片。  
  14.   
  15. 以下三个语法全部是等价的:  
  16. $(document).ready(handler)  
  17. $().ready(handler) (this is not recommended)  
  18. $(handler)  
  19.   
  20. $(document).bind("ready", handler)的行为类似于ready方法,但有一个例外:如果ready事件已触发后,再尝试用.bind("ready")绑定的处理函数将不会被执行。而用以上三种方法绑定的句柄会被执行,无论是什么时候绑定的。  
  21. .ready()方法只能在包含了document的jQuery上调用,因此选择器可以省略。  
  22. .ready()方法典型的用法是使用一个匿名函数:  
  23. $(document).ready(function() {  
  24.   // Handler for .ready() called.  
  25. });  
  26. 等价于:  
  27. $(function() {  
  28.  // Handler for .ready() called.  
  29. });  
  30.   
  31. 如果在DOM加载完毕之后调用.ready(),新的句柄将会立即执行。  
  32.   
  33. 特别强调两点:  
  34. 1.  之所以花费篇幅来阐述.ready()如何使用(翻译的官网文档),是因为网络上的一些文章和中文API不准确甚至是错误的(应该是工具翻译的),所以需谨慎参考  
  35. 2.  .ready()的触发要早于.load(),但是不要完全迷信依赖,如果要获取或操作样式,或依赖于像图片这样的必须加载完成才能获取和操作的元素,就用<body οnlοad="">或.load()  
  36.   
  37. 10.8.2  源码分析  
  38.   
  39. 到底.ready()是如何实现更快的触发ready事件的呢,关键在jQuery.bindReady方法的实现里:  
  40.   
  41. 对标准浏览器绑定:document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );  
  42. 对IE浏览器绑定:document.attachEvent( "onreadystatechange", DOMContentLoaded );  
  43.   
  44. 当然还有一些其他的关键技巧,现在完成的看下jQuery.bindReady的实现:  
  45.   
  46. // 绑定DOM ready监听器,跨浏览器,兼容标准浏览器和IE浏览器  
  47. bindReady: function() { // jQuery.bindReady  
  48. if ( readyList ) {  
  49.     return;  
  50. }  
  51.   
  52. readyList = jQuery._Deferred(); // 初始化ready异步事件句柄队列  
  53.   
  54. // Catch cases where $(document).ready() is called after the  
  55. // browser event has already occurred.  
  56. // 如果DOM已经完毕,立即调用jQuery.ready  
  57. if ( document.readyState === "complete" ) {  
  58.     // Handle it asynchronously to allow scripts the opportunity to delay ready  
  59.     // 重要的是异步  
  60.     return setTimeout( jQuery.ready, 1 );  
  61. }  
  62.   
  63. // Mozilla, Opera and webkit nightlies currently support this event  
  64. // DOM 2级事件模型,Mozilla, Opera, webkit等   
  65. if ( document.addEventListener ) {  
  66.     // Use the handy event callback  
  67.     // 使用快速事件句柄  
  68.     document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );  
  69.   
  70.     // A fallback to window.onload, that will always work  
  71.     // 再在window上绑定load事件句柄,这个句柄总是会执行  
  72.     // 为什么同时绑定document window呢?我想是为了安全起见,防御性编码!  
  73.     window.addEventListener( "load", jQuery.ready, false );   
  74.   
  75. // If IE event model is used  
  76. else if ( document.attachEvent ) {  
  77.     // ensure firing before onload,  
  78.     // maybe late but safe also for iframes  
  79.     // 同样onreadystatechange会在onload之前触发,但是对于iframe会有延迟但安全一定会触发  
  80.     // 看看DOMContentLoaded的实现,是检测document.readyState的状态是否为complete,这里有些像ajax中的检测   
  81.     document.attachEvent( "onreadystatechange", DOMContentLoaded );  
  82.   
  83.     // A fallback to window.onload, that will always work  
  84.     window.attachEvent( "onload", jQuery.ready ); // 同样的安全起见,防御性编码!及时前边的所以hack技巧失败了,onload是最后的保障  
  85.   
  86.     // If IE and not a frame  
  87.     // continually check to see if the document is ready  
  88.     var toplevel = false;  
  89.   
  90.     try {  
  91.         toplevel = window.frameElement == null// 检查window的frameElement属性,看是否是顶层窗口  
  92.     } catch(e) {}  
  93.       
  94.     // 做doScroll检测,如果在iframe中就不检测了,onreadystatechange对于ifame很可靠  
  95.     if ( document.documentElement.doScroll && toplevel ) {  
  96.         doScrollCheck();   
  97.         // 在doScrollCheck中,不断的(每隔1ms)执行document.documentElement.doScroll("left"),直到不抛出异常为止  
  98.         // 这是IE下检测DOM ready的技巧  
  99.         }  
  100.     }  
  101. },  
  102.   
  103. 然后是完整的ready相关方法的分析,我剔除了其他无关的代码,保留了整体结构和相关方法:  
  104.   
  105. (function( window, undefined ) {  
  106.   
  107.     var jQuery = (function() {  
  108.           
  109.         // Define a local copy of jQuery  
  110.         var jQuery = function( selector, context ) {  
  111.                 // The jQuery object is actually just the init constructor 'enhanced'  
  112.                 var ret = new jQuery.fn.init( selector, context, rootjQuery );  
  113.                 return ret;  
  114.             },  
  115.           
  116.             // A central reference to the root jQuery(document)  
  117.             rootjQuery, // 包含了document的jQuery对象  
  118.           
  119.             // The deferred used on DOM ready  
  120.             readyList, // ready事件处理函数队列  
  121.           
  122.             // The ready event handler  
  123.             DOMContentLoaded, // DOM ready hack句柄,.ready()能早于.load()的关键所在  
  124.           
  125.         jQuery.fn = jQuery.prototype = {  
  126.             constructor: jQuery,  
  127.             init: function( selector, context, rootjQuery ) {  
  128.                 // HANDLE: $(function)  
  129.                 // Shortcut for document ready  
  130.                 // 如果函数,则认为是DOM ready句柄  
  131.                 if ( jQuery.isFunction( selector ) ) {  
  132.                     return rootjQuery.ready( selector );  
  133.                 }  
  134.             },  
  135.           
  136.             ready: function( fn ) {  
  137.                 // Attach the listeners  
  138.                 jQuery.bindReady(); // 绑定DOM ready监听器,跨浏览器,兼容标准浏览器和IE浏览器  
  139.           
  140.                 // Add the callback  
  141.                 readyList.done( fn ); // 将ready句柄添加到ready异步句柄队列  
  142.           
  143.                 return this;  
  144.             }  
  145.         };  
  146.           
  147.         // Give the init function the jQuery prototype for later instantiation  
  148.         jQuery.fn.init.prototype = jQuery.fn;  
  149.           
  150.         jQuery.extend = jQuery.fn.extend = function() {};  
  151.           
  152.         jQuery.extend({  
  153.           
  154.             // Is the DOM ready to be used? Set to true once it occurs.  
  155.             // DOM是否加载完毕  
  156.             isReady: false,  
  157.           
  158.             // A counter to track how many items to wait for before  
  159.             // the ready event fires. See #6781  
  160.             // DOM加载完毕之前的等待次数  
  161.             readyWait: 1,  
  162.           
  163.             // Hold (or release) the ready event  
  164.             // 是否延迟触发DOM ready?在jQuery中没有任何地方有调用到holdReady,这是个遗留方法还是预留方法,有待继续研究。  
  165.             // 因此在当前版本中,readyWait总是1,直到自减  
  166.             holdReady: function( hold ) {  
  167.                 if ( hold ) { // 继续等待  
  168.                     jQuery.readyWait++;  
  169.                 } else {  
  170.                     jQuery.ready( true ); //   
  171.                 }  
  172.             },  
  173.           
  174.             // Handle when the DOM is ready  
  175.             // 判断DOM是否加载完毕,如果已完毕,调用DOM ready事件异步队列readyList,如果未完,每个1ms检查一次  
  176.             ready: function( wait ) { // jQuery.ready  
  177.                 // Either a released hold or an DOMready/load event and not yet ready  
  178.                 // 条件1:wait === true && !--jQuery.readyWait 还在等待加载完成,但是等待计数器jQuery.readyWait已经是0,  
  179.                 // 换句话说参数wait为true表示尝试一下看是否能开始调用readyList了,如果发现计数器jQuery.readyWait变成0,啥也不管了,开始调用吧  
  180.                 // 再换句话说,计数器已经是0了,开干吧  
  181.                   
  182.                 // 条件2:wait !== true && !jQuery.isReady 明确的说不用了,即使DOM ready标记还是false  
  183.                 // 换句话说,及时DOM ready标记还是false,但是调用jQuery.ready的客户端认为不比再等了,可以开干了  
  184.                 if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) {  
  185.                     // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).  
  186.                     // 检查document.body是否存在,这是特定于IE的检测,保证在IE中DOM ready事件能正确判断  
  187.                     if ( !document.body ) {  
  188.                         return setTimeout( jQuery.ready, 1 );  
  189.                     }  
  190.   
  191.                     // Remember that the DOM is ready  
  192.                     // 到这里,jQuery.isReady被强制为true  
  193.                     // 在条件2中,如果jQuery.isReady为true,那么说明readyList的状态已经确定,添加到readyList中的函数会被立即执行  
  194.                     // 如果jQuery.isReady为是false,那么接下来readyList中函数将被执行  
  195.                     jQuery.isReady = true;  
  196.   
  197.                     // If a normal DOM Ready event fired, decrement, and wait if need be  
  198.                     // 虽然上边的条件2的意思是,别管其他情况,调用方认为ready了,但是这里仍然做了防御性检测,如果等待计数器仍然大于1,结束ready调用  
  199.                     // 如果这个条件成立,那么一定是哪里出问题了!  
  200.                     // 在当前版本1.6.1中,这个判断永远不会成立,因为没有地方调用会使readyWait加一的holdReady  
  201.                     if ( wait !== true && --jQuery.readyWait > 0 ) {  
  202.                         return;  
  203.                     }  
  204.   
  205.                     // If there are functions bound, to execute  
  206.                     // 调用DOM ready事件异步队列readyList,注意整理的参数亮了!  
  207.                     // document指定了ready句柄的上下文,这样我们在执行ready事件句柄时this指向document  
  208.                     // [ jQuery ]指定了ready事件句柄的第一个参数,这样即使调用$.noConflict()交出了$的控制权,我们依然可以将句柄的第一个参数命名为$,继续在句柄内部使用$符号  
  209.                     // 如此的精致!赞叹之余,但不要在你的项目中也这么用,理解和维护将成为最大的难题。  
  210.                     readyList.resolveWith( document, [ jQuery ] );  
  211.   
  212.                     // Trigger any bound ready events  
  213.                     if ( jQuery.fn.trigger ) {  
  214.                         // 触发ready事件,然后删除ready句柄,DOM ready事件只会生效一次,ready是个自定义事件  
  215.                         jQuery( document ).trigger( "ready" ).unbind( "ready" );  
  216.                     }  
  217.                 }  
  218.             },  
  219.           
  220.             // 绑定DOM ready监听器,跨浏览器,兼容标准浏览器和IE浏览器  
  221.             bindReady: function() { // jQuery.bindReady  
  222.                 if ( readyList ) {  
  223.                     return;  
  224.                 }  
  225.   
  226.                 readyList = jQuery._Deferred(); // 初始化ready异步事件句柄队列  
  227.   
  228.                 // Catch cases where $(document).ready() is called after the  
  229.                 // browser event has already occurred.  
  230.                 // 如果DOM已经完毕,立即调用jQuery.ready  
  231.                 if ( document.readyState === "complete" ) {  
  232.                     // Handle it asynchronously to allow scripts the opportunity to delay ready  
  233.                     // 重要的是异步  
  234.                     return setTimeout( jQuery.ready, 1 );  
  235.                 }  
  236.   
  237.                 // Mozilla, Opera and webkit nightlies currently support this event  
  238.                 // DOM 2级事件模型,Mozilla, Opera, webkit等   
  239.                 if ( document.addEventListener ) {  
  240.                     // Use the handy event callback  
  241.                     // 使用快速事件句柄  
  242.                     document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );  
  243.   
  244.                     // A fallback to window.onload, that will always work  
  245.                     // 再在window上绑定load事件句柄,这个句柄总是会执行  
  246.                     // 为什么同时绑定document window呢?我想是为了安全起见,防御性编码!  
  247.                     window.addEventListener( "load", jQuery.ready, false );   
  248.   
  249.                 // If IE event model is used  
  250.                 } else if ( document.attachEvent ) {  
  251.                     // ensure firing before onload,  
  252.                     // maybe late but safe also for iframes  
  253.                     // 同样onreadystatechange会在onload之前触发,但是对于iframe会有延迟但安全一定会触发  
  254.                     // 看看DOMContentLoaded的实现,是检测document.readyState的状态是否为complete,这里有些像ajax中的检测   
  255.                     document.attachEvent( "onreadystatechange", DOMContentLoaded );  
  256.   
  257.                     // A fallback to window.onload, that will always work  
  258.                     window.attachEvent( "onload", jQuery.ready ); // 同样的安全起见,防御性编码!及时前边的所以hack技巧失败了,onload是最后的保障  
  259.   
  260.                     // If IE and not a frame  
  261.                     // continually check to see if the document is ready  
  262.                     var toplevel = false;  
  263.   
  264.                     try {  
  265.                         toplevel = window.frameElement == null// 检查window的frameElement属性,看是否是顶层窗口  
  266.                     } catch(e) {}  
  267.                       
  268.                     // 做doScroll检测,如果在iframe中就不检测了,onreadystatechange对于ifame很可靠  
  269.                     if ( document.documentElement.doScroll && toplevel ) {  
  270.                         doScrollCheck();   
  271.                         // 在doScrollCheck中,不断的(每隔1ms)执行document.documentElement.doScroll("left"),直到不抛出异常为止  
  272.                         // 这是IE下检测DOM ready的技巧  
  273.                     }  
  274.                 }  
  275.             },  
  276.         });  
  277.           
  278.         // All jQuery objects should point back to these  
  279.         rootjQuery = jQuery(document);  
  280.           
  281.         // Cleanup functions for the document ready method  
  282.         // 构造浏览器加载完毕事件处理函数DOMContentLoaded,需要检测浏览器添加事件的方法  
  283.         if ( document.addEventListener ) {  
  284.             DOMContentLoaded = function() {  
  285.                 // document ready之后移除  
  286.                 document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );  
  287.                 jQuery.ready();  
  288.             };  
  289.           
  290.         } else if ( document.attachEvent ) {  
  291.             DOMContentLoaded = function() {  
  292.                 // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).  
  293.                 // 确保body是存在的,IE会有一些延迟  
  294.                 if ( document.readyState === "complete" ) {  
  295.                     // 先移除,难道浏览器重新渲染会再次触发onreadystatechange吗?  
  296.                     document.detachEvent( "onreadystatechange", DOMContentLoaded );  
  297.                     jQuery.ready();  
  298.                 }  
  299.             };  
  300.         }  
  301.           
  302.         // The DOM ready check for Internet Explorer  
  303.         // 在IE里,每隔1ms检测IE浏览器的ready状态  
  304.         function doScrollCheck() {  
  305.             if ( jQuery.isReady ) {  
  306.                 return;  
  307.             }  
  308.           
  309.             try {  
  310.                 // If IE is used, use the trick by Diego Perini  
  311.                 // http://javascript.nwbox.com/IEContentLoaded/  
  312.                 // 如果是IE,则使用该技巧来检测浏览器加载状态  
  313.                 document.documentElement.doScroll("left");  
  314.             } catch(e) {  
  315.                 setTimeout( doScrollCheck, 1 );  
  316.                 return;  
  317.             }  
  318.           
  319.             // and execute any waiting functions  
  320.             // 执行其他的等待函数  
  321.             jQuery.ready();  
  322.         }  
  323.           
  324.         // Expose jQuery to the global object  
  325.         return jQuery;  
  326.       
  327.     })();  
  328.   
  329.     // 见系列文章中关于异步队列的解析  
  330.     jQuery.extend({  
  331.         // Create a simple deferred (one callbacks list)  
  332.         _Deferred: function() {},  
  333.       
  334.         // Full fledged deferred (two callbacks list)  
  335.         Deferred: function( func ) {},  
  336.       
  337.         // Deferred helper  
  338.         when: function( firstParam ) {}  
  339.     });  
  340.       
  341.     // 见系列文章中关于事件的解析  
  342.     jQuery.event = {  
  343.         trigger: function( event, data, elem, onlyHandlers ) {},  
  344.   
  345.         handle: function( event ) {},  
  346.   
  347.         special: {  
  348.             ready: {  
  349.                 // Make sure the ready event is setup  
  350.                 setup: jQuery.bindReady,  
  351.                 teardown: jQuery.noop  
  352.             }  
  353.         }  
  354.     };  
  355.   
  356.     window.jQuery = window.$ = jQuery;  
  357. })(window);  
 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值