jQuery源码分析(二十): ready与load事件

前端基础 2016-12-13

起步

在jq中在文档载入完毕后有这几种方式去执行指定函数:

$(document).ready(function() {
    // ...代码...
});
//document ready 简写
$(function() {
    // ...代码...
});
$(window).load(function() {
    // ...代码...
});

$(function(){}) 的方式其实是 $(document).ready() 的简写,具体可以看看jq构造器那块。

ready与load谁先执行

这个问题在面试的时候也会经常被提到,ready是先执行的,load后执行,DOM文档的加载步骤:

(1) 解析HTML结构。
(2) 加载外部脚本和样式表文件。
(3) 解析并执行脚本代码。
(4) 构造HTML DOM模型。//ready
(5) 加载图片等外部文件。
(6) 页面加载完毕。//load

分析原因

.ready() 都是作用于 window.onload 事件的。

jQuery.fn.ready = function( fn ) {
    // Add the callback
    jQuery.ready.promise().done( fn );

    return this;
};

当调用ready事件时,jq就会把函数添加到一个回调列表中。

.load() 作用的是为每个匹配元素的load事件绑定处理函数。

$(window).load( function(){
    alert("文档加载完毕!");
} );

$("img").load( function(){
    alert( "图片[" + this.alt +  "]加载完毕!" );
} );

load的写法也发生了一些改变,1.8版本之后,load就抛弃了,只剩下ajax的load了,它的函数原型:

jQuery.fn.load = function( url, params, callback ) {
    //code...
    return this;
}

因此在高版本中它的使用应该是:

$('#result').load('ajax/test.html', function() {
  alert('Load was performed.');
});

ready源码

// readyList.promise() === jQuery.ready.promise()
var readyList;

jQuery.fn.ready = function( fn ) {
    // promise后添加回调
    jQuery.ready.promise().done( fn );
    // 链式
    return this;
};

jQuery.extend({
    // 此判断防止重复触发
    isReady: false,

    // 需要几次jQuery.ready()调用,才会触发promise和自定义ready事件
    readyWait: 1,

    // Hold (or release) the ready event
    holdReady: function( hold ) {
        if ( hold ) {
            // true,延迟次数 +1
            jQuery.readyWait++;
        } else {
            // 无参数,消减次数 -1
            jQuery.ready( true );
        }
    },

    // 触发promise和自定义ready事件
    ready: function( wait ) {

        // ready(true)时,消减次数的地方。也能代替干ready()的事
        if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
            return;
        }

        // ready()调用时,标记dom已加载完成
        jQuery.isReady = true;

        // ready()能够设置isReady,只能消减默认的那1次
        if ( wait !== true && --jQuery.readyWait > 0 ) {
            return;
        }

        // 触发promise,jQuery.fn.ready(fn)绑定函数都被触发
        readyList.resolveWith( document, [ jQuery ] );

        // 触发自定义ready事件,并删除事件绑定
        if ( jQuery.fn.triggerHandler ) {
            jQuery( document ).triggerHandler( "ready" );
            jQuery( document ).off( "ready" );
        }
    }
});

 // 解绑函数
function completed() {
    document.removeEventListener( "DOMContentLoaded", completed, false );
    window.removeEventListener( "load", completed, false );
    jQuery.ready();
}

jQuery.ready.promise = function( obj ) {
    if ( !readyList ) {

        readyList = jQuery.Deferred();

        // 判断执行到这时,是否已经加载完成
        if ( document.readyState === "complete" ) {
            // 不再需要绑定任何监听函数,直接触发jQuery.ready。延迟一会,等代码执行完
            setTimeout( jQuery.ready );

        } else {

            // Use the handy event callback
            document.addEventListener( "DOMContentLoaded", completed, false );

            // 个别浏览器情况,错过了事件仍可触发
            window.addEventListener( "load", completed, false );
        }
    }
    return readyList.promise( obj );
};

// 执行。生成deferred对象,绑定好监听逻辑
jQuery.ready.promise();

readyList = jQuery.Deferred(); 回调列表是 $.Deferred 对象,事件监听通过:

document.addEventListener( "DOMContentLoaded", completed, false );

window.addEventListener( "load", completed, false );

为了达到兼容不同浏览器,一个添加到window一个添加到document,通过$.isReady 避免多次调用。

function completed() {
    document.removeEventListener( "DOMContentLoaded", completed, false );
    window.removeEventListener( "load", completed, false );
    jQuery.ready();
}

对事件进行解绑,调用$.ready():

readyList.resolveWith( document, [ jQuery ] );

执行异步对象里的函数队列。

load

load的实现就比较简单,jQuery.fn.load = function( url, params, callback ){}它几乎与 $.get(url, data, success) 等价,因为它就是通过$.ajax() 实现的,不同的是它的url可以包含一个空格或多个空格,紧接第一个空格的字符串则是决定所加载内容的 jQuery 选择器:

$("#result").load("ajax/test.html #container");

如果执行该方法,则会取回 ajax/test.html 的内容,不过然后,jQuery 会解析被返回的文档,来查找带有容器 ID 的元素。该元素,连同其内容,会被插入带有结果 ID 的元素中,所取回文档的其余部分会被丢弃。

off = url.indexOf(" ");

if ( off >= 0 ) {
    selector = jQuery.trim( url.slice( off ) );
    url = url.slice( 0, off );
}

最后来看看它ajax部分:

jQuery.ajax({
    url: url,

    // if "type" variable is undefined, then "GET" method will be used
    type: type,
    dataType: "html",
    data: params
}).done(function( responseText ) {

    // Save response for use in complete callback
    response = arguments;

    self.html( selector ?

        // If a selector was specified, locate the right elements in a dummy div
        // Exclude scripts to avoid IE 'Permission Denied' errors
        jQuery("<div>").append( jQuery.parseHTML( responseText ) ).find( selector ) :

        // Otherwise use the full result
        responseText );

}).complete( callback && function( jqXHR, status ) {
    self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );
});

self.html() 是使用浏览器的 .innerHTML 属性来解析被取回的文档,并把它插入当前文档。

总结

readyload的区别就在于资源文件的加载,ready构建了基本的DOM结构,所以对于代码来说应该越快加载越好,因此ready作用对象是文档对象。load是对元素作用的,图片资源过多load事件就会迟迟不会触发。


本文由 hongweipeng 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

如果对您有用,您的支持将鼓励我继续创作!