前言

我们经常会用到$(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()结果如下:

jquery方法中加载js文件 jquery中加载文档的方法_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的介绍将会在后续文章中进行阐述。