前言
上篇文章讲解了jQuery的链式操作,接下来我们来看一下在jQuery中使用最频繁的代码:
$(function(){
***
})
这段代码的意思是指当页面dom加载完毕之后会自动调用这个匿名函数。
我们来看一下源码是怎么实现的
源码
/**源码90行**/
completed = function() {
document.removeEventListener( "DOMContentLoaded", completed, false );
window.removeEventListener( "load", completed, false );
jQuery.ready();
};
/**源码96行**/
jQuery.fn = jQuery.prototype = {
/**源码101行**/
init: function( selector, context, rootjQuery ) {
***
/**源码184行**/
} else if ( jQuery.isFunction( selector ) ) {
return rootjQuery.ready( selector );
}
},
/**源码239行**/
ready: function( fn ) {
// Add the callback
jQuery.ready.promise().done( fn );
return this;
}
}
/**源码348行**/
jQuery.extend({
/**源码381行**/
ready: function( wait ) {
***
/**源码397行**/
readyList.resolveWith( document, [ jQuery ] );
}
}
/**源码818行**/
jQuery.ready.promise = function( obj ) {
if ( !readyList ) {
readyList = jQuery.Deferred();
if ( document.readyState === "complete" ) {
setTimeout( jQuery.ready );
} else {
document.addEventListener( "DOMContentLoaded", completed, false );
window.addEventListener( "load", completed, false );
}
}
return readyList.promise( obj );
};
/**源码865行**/
rootjQuery = jQuery(document);
我们从上篇文章已经得知$()最终创建的是 jQuery.fn.init 的实例。
我们调用$(function(){})。最终init的第一个参数selector就是我们传入的回调函数。
在源码184行可以看到jQuery通过判断selector是函数又调用了rootjQuery.ready( selector )。
从源码865行可以得知rootjQuery 其实就是 jQuery(document) 等价于 $(document)。
这个时候我们就可以猜测jQuery原型下面应该挂载了一个ready方法。
当然我们的猜测是正确的 我们可以通过源码239行看到这个ready方法。
最终调用了jQuery.ready.promise().done( fn )。
我们可以从818行找到jQuery.ready.promise函数,可以看到函数最终return了一个promise对象(jQuery自定义的promise非es6中自带的Promise)。
最终调用了jQuery.ready();
相信很多刚开始看的同学可能已经看糊涂了(我一开始也搞得很迷糊),不要慌,接下来我们再来重新理一下代码的执行过程和逻辑,不懂的代码就要不断的去重新整理重新阅读。或者使用调试工具跑一遍看看。
$(function(){}) ->
return new jQuery.fn.init( selector, context, rootjQuery );->
return rootjQuery.ready( selector );->//到这一步为止jQuery就进行了一些简单判断相信大家都能看得懂
jQuery.ready.promise() //执行该函数时候内部进行了一些判断我们来一起看一下
函数
1、第一步先判断readList是否存在,可以通过源码26行看到定义了readList并没有赋值,所以第一次执行该函数的时候条件成立。然后给readyList赋值为一个 Deferred 对象(后续会介绍Deferred的实现原理)
2、接着又判断了document.readyState === "complete" 这一步的作用是,有可能当执行到这段代码的时候dom已经加载完毕了,这个时候document.readyState的值为‘complete’。这个时候其实我们就可以直接调用回调函数了,这里jQuery利用setTimeout调用了jQuery.ready。else 就是来处理当dom还没加载完毕的逻辑。我们发现jQuery监听了document 的 ‘DOMContentLoaded’ 和 window的‘load’ 两个事件监听(兼容IE老版本有的时候load事件会比DOMContentLoaded来的快)。jQuery为了确保第一时间执行回调函数所以监听了两个事件。然后最后返回promise对象。
3、接下里我们来看下completed函数,先清除了两个监听事件(防止重复调用)然后再最终也调用了jQuery.ready。
4、最后我们来看一下jQuery.ready函数,先忽略其他代码看到397行,我们可以看到readyList.resolveWith来最终执行了回调函数。也就是自己传入的匿名函数。当然这块匿名函数是通过
jQuery.ready.promise().done( fn );传入的。
所以最终可以把
解释
可能部分同学没有使用过jQuery的Deferred方法,接下来简单介绍一下Deferred方法的使用,后续再对源码进行详细剖析。
var def = $.Deferred()
var promise = def.promise();
promise.done(function(){
console.log(1);
});
setTimeout(function(){
def.resolveWith( document );
}, 3000)
这段代码会在3秒之后在控制台打印出1。
dfe大家可以先理解成改状态用的,promise大家可以理解成添加回调方法用的。(后续介绍延迟对象的时候会详解)
done可以看成是添加成功回调函数的方法(对应也有添加失败的方法)。在resolve或者resolveWith会依次执行(resolve和resolveWith的区别是resolveWith可以改变函数执行时候的this指向,resolve最终调用的其实还是resolveWith)。
resolveWith其实就是改变状态(可以看成会循环触发done里面添加的函数)
其他
/**源码400行**/
if ( jQuery.fn.trigger ) {
jQuery( document ).trigger("ready").off("ready");
}
//以上代码处理的是通过事件形式绑定的回调,例如:
$(document).on('ready', function(){
***
})
再看jQuery.ready方法的其他判断代码:
/**源码372行**/
holdReady: function( hold ) {
if ( hold ) {
jQuery.readyWait++;
} else {
jQuery.ready( true );
}
}
/**源码384行**/
if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
return;
}
jQuery.isReady = true;
if ( wait !== true && --jQuery.readyWait > 0 ) {
return;
}
以上代码处理的是配合$.holdReady(true)来使用的,有的时候我们想在dom加载完毕之后不立即触发回调函数可以通过调用该代码来实现。
其实底层实现就是通过类似计数器方式来实现,利用isReady来存储dom加载状态,计数器的变量名称定义为readyWait
调用$.holdReady(true)就把计数器+1,不传参数或者传false就调用jQuery.ready( true )来让计数器-1,当计数器减到0就执行回调函数。