概述
本文详细分析了jquery-1.11.1.js源码文件行数:3394~3537;
代码简介:
定义了jQuery.fn.ready,本质是使用Deferred实现的一个执行异步回调函数的机制,在dom加载完毕之后,执行绑定的回调函数;
下文是代码的详细分析。
代码分析
// Deferred对象
var readyList;
// JQ对象的ready方法
// 后续代码可以发现,其实无论哪个JQ对象去执行ready方法,实际效果都是一样的,从这里也可以看出上下文没有应用在jQuery.ready里面
// 实际ready方法只有一个readyList,也只使用一个Deferred
jQuery.fn.ready = function( fn ) {
// 实际执行就是将传入的fn添加到readyList中
jQuery.ready.promise().done( fn );
return this;
};
// 扩展工具变量及函数
jQuery.extend({
// 用于判断dom节点是否已经加载完毕
isReady: false,
// 计数变量,每执行一次holdReady都会加1
readyWait: 1,
// hold不为空则增加readyWait,否则触发一次ready
holdReady: function( hold ) {
if ( hold ) {
jQuery.readyWait++;
} else {
jQuery.ready( true );
}
},
// 定义工具方法ready
// 当JQ对象调ready方法时,会执行jQuery.ready.promise,该方法里面实际就是执行本ready方法
ready: function( wait ) {
// 如果--jQuery.readyWait不为0则返回,表示还需要等待,需要继续触发ready
if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
return;
}
// 异步递归兼容某些bug防止document.body没加载完
if ( !document.body ) {
return setTimeout( jQuery.ready );
}
// 设置isReady为true
jQuery.isReady = true;
// --jQuery.readyWait > 0则必须继续等待,需要继续触发ready
if ( wait !== true && --jQuery.readyWait > 0 ) {
return;
}
// 执行readyList里的回调
readyList.resolveWith( document, [ jQuery ] );
// 触发ready函数执行绑定的其他回调
if ( jQuery.fn.triggerHandler ) {
jQuery( document ).triggerHandler( "ready" );
jQuery( document ).off( "ready" );
}
}
});
/**
* 去除事件监听
*/
function detach() {
if ( document.addEventListener ) {
document.removeEventListener( "DOMContentLoaded", completed, false );
window.removeEventListener( "load", completed, false );
} else {
document.detachEvent( "onreadystatechange", completed );
window.detachEvent( "onload", completed );
}
}
/**
* ready事件回调函数
*/
function completed() {
// IE也支持document.readyState
// 这里使用event.type我感到不解,函数没有形参,应该使用arguments去获取事件,这感觉像是代码缺陷
if ( document.addEventListener || event.type === "load" || document.readyState === "complete" ) {
// 因为注册了两个监听器,因此先调用detach去除事件监听,以免多次执行回调
detach();
// 执行工具函数ready
jQuery.ready();
}
}
定义jQuery.ready.promise函数,当JQ对象调用ready函数时,实际就是执行这个函数
jQuery.ready.promise = function( obj ) {
// 判断readyList是否为空,没有则进入
if ( !readyList ) {
// 初始化readyList
readyList = jQuery.Deferred();
// 使用document.readyState的值判断是否可触发延迟回调
if ( document.readyState === "complete" ) {
// 使用setTimeout制造异步,保证jQuery.ready在当前一段js代码跑完后再执行
setTimeout( jQuery.ready );
// document.readyState值不为"complete",则监听事件,
} else if ( document.addEventListener ) {
// 监听DOMContentLoaded事件
document.addEventListener( "DOMContentLoaded", completed, false );
// 同时监听load事件,是为了保证JQ对象调用ready时,实际页面已经处于完全加载的状态
// 某些浏览器onload有可能优先执行,就没有执行DOMContentLoaded了
window.addEventListener( "load", completed, false );
// 兼容IE
} else {
document.attachEvent( "onreadystatechange", completed );
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();
}
})();
}
}
}
// 返回Deferred里的promise对象
return readyList.promise( obj );
};