首先阅读Deferred相关知识:
测试1;
$(document).ready(function(){ alert("ready"); }); //这种方式调用以后,还要通过triggerHander来触发文档的ready事件!所以这个例子会执行两次ready! //触发以后就要移除这个事件!因为ready事件不要多次调用! //而且该函数中的this指向的是HTMLDocument对象,参数是jQuery对象,也就是构造函数,源码readyList.resolveWith(document,[jQuery]) $(function(args) { alert("ready"+this+"->"+args); })测试2:(调用jQuery.holdReady延迟)
//这时候的readyWait已经是2了,所以wait !== true && --jQuery.readyWait > 0是false,最后就是不执行! //记住:这时候的jQuery.readyWait已经是1了,因为已经自减了! //如何解除禁止呢? jQuery.holdReady(1); $(document).ready(function(){ alert("ready"); }); //这种方式调用以后,还要通过triggerHander来触发文档的ready事件!所以这个例子会执行两次! //触发以后就要移除这个事件!因为ready事件不要多次调用! //而且该函数中的this指向的是HTMLDocument对象,参数是jQuery对象,也就是构造函数 $(function(args) { alert("ready"+this+"->"+args); }) //这时候相当于调用jQuery.ready( true );这时候 wait === true ? --jQuery.readyWait : jQuery.isReady是false //这时候就会执行上面两次的ready事件! jQuery.holdReady();
note:方法holdReady调用时候加入参数相当于进行添加一层禁止,如果不传入参数表示解除一层禁止!
ready方法源码:
var readyList; jQuery.fn.ready = function( fn ) { // Add the callback jQuery.ready.promise().done( fn ); return this; };jQuery.ready方法:
jQuery.extend({ // Is the DOM ready to be used? Set to true once it occurs. isReady: false, // A counter to track how many items to wait for before // the ready event fires. See #6781 readyWait: 1, // Hold (or release) the ready event holdReady: function( hold ) { if ( hold ) { jQuery.readyWait++; } else { jQuery.ready( true ); } }, // Handle when the DOM is ready ready: function( wait ) { //如果readyWait不是0,或者isReady是false,那么什么也不做,继续等待! // Abort if there are pending holds or we're already ready if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { return; } //在IE中需要判断body是否存在,不存在那么等待! // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). if ( !document.body ) { return setTimeout( jQuery.ready ); } //如果load事件或者contentLoad事件已经完成,那么把isReay设置为true! // Remember that the DOM is ready jQuery.isReady = true; //一直等到readyWait为0 // If a normal DOM Ready event fired, decrement, and wait if need be if ( wait !== true && --jQuery.readyWait > 0 ) { return; } //如果已经readyWait是0,那么就会执行,也就是触发事件,最终调用Deferred对象的done方法 //上下文是document,第二个参数是jQuery对象!这时候外层的done已经执行了,也就说这个ready中的函数可以执行了! //第一个参数是指向context所以ready中第一个参数是document对象,第二个jQuery就是参数也就是给ready函数传递的参数! // If there are functions bound, to execute readyList.resolveWith( document, [ jQuery ] ); //还要调用ready事件函数! // Trigger any bound ready events if ( jQuery.fn.triggerHandler ) { jQuery( document ).triggerHandler( "ready" ); jQuery( document ).off( "ready" ); } } });detach源码:(DOMContentedLoaded或者load只要有一个触发就清除事件)
function detach() { if ( document.addEventListener ) { document.removeEventListener( "DOMContentLoaded", completed, false ); window.removeEventListener( "load", completed, false ); } else { document.detachEvent( "onreadystatechange", completed ); window.detachEvent( "onload", completed ); } }complete完成的源码:(只要事件类型是event.type=load或者document.readystate是complete)
function completed() { // readyState === "complete" is good enough for us to call the dom ready in oldIE //如果触发了load或者complete事件就移除相应的事件! if ( document.addEventListener || event.type === "load" || document.readyState === "complete" ) { detach(); jQuery.ready(); } }jQuery.ready.promise对象:(其实返回的是一个promise对象,该对象没有状态改变的方法,其中有三个Callbacks对象)
jQuery.ready.promise = function( obj ) { //这里面的readyList是全局变量,所以可以在多个方法中共享!而且执行一次过后就不会被再次执行了! if ( !readyList ) { readyList = jQuery.Deferred(); // 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 if ( document.readyState === "complete" ) { //如果DOM已经完成,直接调用jQuery.ready方法,但是setTimeout没有第二个参数! //这是为了防止IE的! // Handle it asynchronously to allow scripts the opportunity to delay ready setTimeout( jQuery.ready ); // Standards-based browsers support DOMContentLoaded } else if ( document.addEventListener ) { //为什么要同时检测DOMContentLoaded和load,因为有些浏览器中会缓存load事件 //所以会先触发load,后触发DOMContentLoaded,所以看那个更快就行! // Use the handy event callback document.addEventListener( "DOMContentLoaded", completed, false ); // A fallback to window.onload, that will always work window.addEventListener( "load", completed, false ); // If IE event model is used } else { // 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 { //获取window中的<iframe>或者<object>对象 //如果页面中没有相应的iframe或者object那么获取documentElement对象! top = window.frameElement == null && document.documentElement; } catch(e) {} //还好ie有个特有的doScroll方法,当页面DOM未加载完成时,调用doScroll方法时, //就会报错,反过来,只要一直间隔调用doScroll直到不报错,那就表示页面DOM加载完毕了, //不管图片和iframe中的内容是否加载完毕,此法都有效。 if ( top && top.doScroll ) { (function doScrollCheck() { if ( !jQuery.isReady ) { //没有ready的时候那么就会调用documentElement的doScroll()方法 //不断调用这个方法,如果报错了就每隔50毫秒调用一次,如果不报错 //表示DOM加载完成,这时候就只要把所有的事件移除,然后调用ready方法就可以了 //这就是IE6-8的DOMContentLoaded事件的结果方法! 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(); } })(); } } } //让返回的对象obj继承promise的所有的方法! //所以该对象相当于一个promise对象,我们知道promise和Deferred是相同的,只是前者没有改变状态的方法, //所以归根到底,这个promise方法就是对应于一个$.Callbacks对象,而这个promise的done方法对应于其中的add方法! //所以就是把所有的事件放在内部的一个list中,这个list中的所有函数要通过resolve或者resolveWith调用! return readyList.promise( obj ); };
下面给出几个holdReady的例子:
例1:
$.holdReady(true); $(function() { alert("x");//不会马上打印x,因为已经holdReady了! })例2:(假如a.js中alert(1),为了先打印1,后打印2,怎么做)
$.holdReady(true); $.getScript("a.js",function(){ $.holdReady(false);})//释放 $(function(){alert(2)})//这时候就会先弹出1(a.js中打印1),后弹出2例3:要等到所有的holdReady都减少为1.因为每次只是释放一次
$.holdReady(true); $.getScript("a.js",function(){ $.holdReady(false);})//释放 $.holdReady(true); $.getScript("b.js",function(){ $.holdReady(false);})//释放 $.holdReady(true); $.getScript("c.js",function(){ $.holdReady(false);})//释放 $(function(){alert("xxxx");}) // 这时候readyWait就是3了!必须释放三次,也就是a.js,b.js.c.js全部加载完成才行!
总结:
(1)如果传入了参数那么直接判断jQuery.readyWait的值,如果没有传入参数那么首先判断jQuery.isReady的值,然后判断jQuery.readyWait的值!
(2)window.frameElement可以获取页面中的iframe或者object对象。
(3)要理解到,如果在页面中绑定了多个ready事件,其实是在Deferred对象所对应的Callbacks集合中添加了多个回调函数,所以当你resolve的时候多个方法都会调用!
(4)IE的doScroll方法,当页面DOM未加载完成时,调用doScroll方法时,就会报错,反过来,只要一直间隔调用doScroll直到不报错,那就表示页面DOM加载完毕了,不管图片和iframe中的内容是否加载完毕,此法都有效。
(5)document对象有DOMContentLoaded事件,window有onload事件。如果用attachEvent就要用document的onreadyStateChange进行监听,而window还是用load事件!