jQuery源码分析之ready方法

首先阅读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事件!

你可能感兴趣的:(jQuery源码分析之ready方法)