前言
我们经常会用到$(document).ready(fn)或者$(fn),可是,我们只是用这个函数来代替window.onload么?其实不是的,文档的加载除了DOM结构树的加载之外还包括其他外部资源如图片或脚本的加载,而所有资源的加载会触发window.onload函数,但我们不可能总等所有资源加载出来再执行接下来的代码,有时候加载的外部资源很大的时候,我们就可以先在DOM结构树加载完之后开始做事了,不用等其他资源加载完毕。
我们可以通过反复监听DOMContentLoaded的方式来实现类似ready的功能。但是$(document).ready(fn)实现的方式不是通过直接监听DOMContentLoaded达到目的的。
大多数浏览器提供了 DOMContentLoaded 事件形式的类似功能。 然而,jQuery的 .ready() 方法的不同之处在于它是一个重要并且有效的方法:在代码调用.ready( handler )之前,如果 DOM 已经准备就绪并且浏览器已经触发DOMContentLoaded,handler处理函数仍然会被执行。 相反,如果 DOMContentLoaded 事件侦听器在这个事件触发后才被添加进来,那么这个DOMContentLoaded 事件的处理程序将永远不会被执行。
有的人会问为什么一定要传个document进去,然而,这不是必然的,也可以写成$().ready(fn)
或者人们常用的$(fn)
,document不是必然的,因为高版本的jQuery已经把document传进去了rootjQuery = jQuery( document )
,更推荐写法$( handler )的简洁方式。
在jQuery 3.0 中,只建议使用第一种语法(愚人码头注:即 $( handler )); 其他语法仍然能正常工作,但已被标记为弃用(愚人码头注:将来的某个版本会被删除)。
核心源码
var readyList = jQuery.Deferred();
3865 jQuery.fn.ready = function( fn ) {
readyList
.then( fn )
// Wrap jQuery.readyException in a function so that the lookup
// happens at the time of error handling instead of callback
// registration.
.catch( function( error ) {
jQuery.readyException( error );
} );
return this;
};
思路分析
- 基本思想:实现文档加载完毕而不是所有资源加载完毕后立刻执行的函数。
- 简便写法:实现$(fn) === $().ready(fn) === $(document).ready(fn),由于跟传入的selector无关,只跟selector是否为function有关,因此应该使用简便写法。
源码分析
1)首先$().ready(fn)
里面调用的应该是一个延迟对象Deferred或Promise,看源码可知是一个Deferred。
jQuery.Deferred() // jQuery.js加载时候就执行
.then( fn ) // 先执行完deferred里面的内容在执行fn
.catch( function( error ) { // 如果fn出错则用jQuery默认的ready异常处理方式
jQuery.readyException( error );
} )
// jQuery使用的ready异常处理方式是保证DOM刷新完毕再把异常抛给window
jQuery.readyException = function( error ) {
window.setTimeout( function() {
throw error;
} );
};
readyException表面上看是多此一举,可这恰恰可以避免异常。
发现没有?ready里面与selector根本没有任何关系,所以不再推荐传入document了。
2)为什么创造一个Deferred出来就可以保证DOM肯定是完全加载的呢?原因是Deferred内部的then方法做了一个setTimeOut的操作,而已做了递归。所以保证了DOM肯定是完全加载,相当于监听了DOMContentLoaded
process = special ?
mightThrow :
function() {
....
if ( depth ) { // 递归
process();
} else {
....
window.setTimeout( process ); // 等待DOMContentLoaded完成
}
}
所以调用$().ready()结果如下:
3)调用$(fn)就等于调用$().ready()
root = rootjQuery = jQuery( document ); // 默认把document传进去了
// selector是方法的话,就是我们平时写的$(fn)
else if ( jQuery.isFunction( selector ) ) {
return root.ready !== undefined ?
root.ready( selector ) : //ready还存在的时候就调用ready
// Execute immediately if ready is not present
selector( jQuery ); // ready不存在的时候就直接调用该方法(不需要等待),这就是我们常用的$(function($))
}
最后总结
由jQuery的版本迭代情况来看,window.onload会逐渐被淘汰,随着网页的体积越来越大和外部资源越来越多,为了用户体验,这个方法基本上是不会使用的了。另外,DOMContentLoaded会逐渐被setTimeout取代。jQuery.Deferred的作用也会越来越强大,关于Deferred的介绍将会在后续文章中进行阐述。