jquery源碼 DOM加載


jQuery版本:2.0.3

DOM加載有關的擴展

  • isReady:DOM是否加載完(內部使用) 
  • readyWait:等待多少文件的計數器(內部使用)
  • holdReady():推遲DOM觸發
  • ready():准備DOM觸發。
  • jQuery.ready.promise=function(){};  監聽DOM的異步操作(內部使用)

一、$(function(){})和原生window.onload的關系

這個在面試中也是經常會被問到的。從下面幾個角度來分析一下它們的區別

1、執行時機

頁面加載,先加載節點,再加載文件,比如img文件,flash等。

$(function(){})DOM加載完執行。可能DOM元素關聯的東西並沒有加載完。

window.onload等節點和文件都加載完執行。

對應的事件監聽

jQuery用的是DOMContentLoaded事件。

DOMDContentLoaded:原生DOM加載事件,這個事件觸發代表DOM加載完了。

我之前寫過一篇文章的頁面加載時間分析里也有提到。

onload對應的是load事件。

2、個數

window.onload不能寫多個,后面的會覆蓋前面的。

$(function(){})可以寫多個。都會執行 。

3、簡化寫法

$(function(){})是$(document) .ready(function(){});的簡化寫法。

window.onload沒有簡化寫法。

二、jQurey如何實現DOM ready的

jQuery直接調用DOMContentLoaded來實現DOM的ready。但是DOMContentLoaded和onLoad一樣,瀏覽器只執行一次,jQuery用什么判斷是否已經執行過呢?document.readyState就是判斷這個的依據。

readyState是document的屬性,總共有3個值:

  • loading:文檔正在加載中
  • interactive:文檔已經加載完成,正在進行css和圖片等資源的加載
  • complete:文檔的所以資源加載完成

判斷完之后如何回調呢?就是用Promise。jQuery通過new一個$.Deferred(promise)對象來實現對DOM的ready的回調,在DOMContentLoaded中將這個promise給resolve掉,這樣就執行了之前注冊的回調函數,同時后面新注冊的回調也會立刻執行。

但是在調用promise之前,jQuery執行了一次setTimeout,因為jQuery.Promise是不會產生異步的,這和標准的promise規范是不一樣的,所有jQuery自己又手動做了一次setTimeout來實現異步。這樣使得無論使用在DOM的ready之前注冊的回調還是之后注冊的回調都會在異步中執行。

三、源碼整體實現邏輯

$(fn)==>new一個 jQuery.fn.init(fn)==>返回$(document).ready( fn)。也就是說

  • $(function(){}) =》
  • 調用$(document).ready(function(){})=》
  • 相當於$().ready(fn)實例方法=》
  • 調用的jQuery.ready.promise().done(fn)=》
  • jQuery.ready.promise中不管if還是else最終都是調用jQuery.ready(),並返回promise=》
  • jQuery.ready()里面重點是readyList.resolveWith( document, [ jQuery ] );已完成。至此DOM加載完畢就可以調用fn了。

 

 

 

四、實現細節

1、重點是:jQuery.ready.promise()方法【巧妙

如果DOM已經加載完成了,調用jQuery.ready()這個工具方法;

如果DOM沒加載完,監聽DOMContentLoaded事件和load事件,等事件發生時回調completed(),最終也是調用jQuery.ready()這個工具方法;

var    // A central reference to the root jQuery(document)
    rootjQuery,

    // The deferred used on DOM ready
    readyList;

jQuery.ready.promise = function( obj ) {
    if ( !readyList ) { //第一次readyList為空可以進來,后續就進不來if了,只執行一次

        readyList = jQuery.Deferred(); //第一步,創建延遲對象
        if ( document.readyState === "complete" ) { //DOM加載完成的標志就是document.readyState為complete,如果DOM已經加載好了就直接調工具方法jQuery.ready。
            setTimeout( jQuery.ready );//加定時器是為了兼容IE
        } else {//DOM沒有加載完檢測,即檢測了DOMContentLoaded事件,也檢測了load事件;最終走回調completed函數
            // Use the handy event callback
            document.addEventListener( "DOMContentLoaded", completed, false );
            // A fallback to window.onload, that will always work
            window.addEventListener( "load", completed, false ); //因為火狐瀏覽器會緩存load事件,為了第一時間相應所以對load也監聽了
        }
    }
    return readyList.promise( obj );
};

completed回調函數如下,最終調用的也是jQuery.ready()。 

    // The ready event handler and self cleanup method
    completed = function() {
    //不管是DOMContentLoaded事件還是load發生,都會取消2個事件監聽
    //jQuery.ready()只會觸發一次
        document.removeEventListener( "DOMContentLoaded", completed, false );
        window.removeEventListener( "load", completed, false );
        jQuery.ready();
    };

2、jQuery.ready()工具方法做了些什么 

先做個測試:

   $(function(arg){
       alert(this); //[object HTMLDocument]
       alert(arg); //jQuery函數
   })

再做個測試:

除了$(function(){});$(document).ready(function(){}),也可以把ready當做事件來處理如下。

<script>
$(document).on("ready",function(){
    alert(123); //123
});
</script>

之所以會自動彈出123,是因為在jQuery源碼中用這句話jQuery( document ).trigger("ready").off("ready");來主動觸發了ready事件,觸發后再取消掉。

// Handle when the DOM is ready
ready: function( wait ) { //參數和holdReady有關

    // Abort if there are pending holds or we're already ready
    if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
        return;
    }

    // Remember that the DOM is ready
    jQuery.isReady = true;

    // If a normal DOM Ready event fired, decrement, and wait if need be
    if ( wait !== true && --jQuery.readyWait > 0 ) {
        return;
    }
    //第一步看這里重點,resolveWith改變狀態的時候傳參了,給done中方法fn傳入了參數,
    //document是fn的this指向,jQuery是參數
    // If there are functions bound, to execute
    readyList.resolveWith( document, [ jQuery ] );
    //跟主動觸發有關
    // Trigger any bound ready events
    if ( jQuery.fn.trigger ) {
        jQuery( document ).trigger("ready").off("ready");
    }
},

跟ready的參數有關的有一個holdReady()。

先做個測試

$.holdReady(true);
$(function () {
    alert(123); //調用了holdReady並傳參true,就不能彈出123了
});

可以推遲,也可以釋放推遲,釋放了以后就可以觸發了。

$.holdReady(true);
$.holdReady(false);
$(function () {
    alert(123); //釋放了holdReady,就彈出123
});

這個有什么用?

比如:

$.getScript('js/a.js',function(){ //異步加載,不會影響后續代碼執行。可能會產生一個問題,alert(2)先執行了a.js還沒有加載完

})

$(function () {
    alert(2);//先彈2,后彈出1
});

很多時候引入外部文件的時候,都想等外部文件或者插件加載完,再去觸發操作,操作可能用到a.js。現在這個順序不對,會出問題。

怎樣解決這個問題?用holdReady()方法。

    $.holdReady(true); //在這里先hold住
    $.getScript('js/a.js', function () { //異步加載,不會影響后續代碼執行。可能會產生一個問題,alert(2)先執行了a.js還沒有加載完
        $.holdReady(false); //加載完釋放,不hold了就可以彈2了
    })

    $(function () {
        alert(2);//先彈2,后彈出1
    });

再深入一點,holdReady() 要針對的文件可能不止一個,有很多個,所以要等所有的文件都加載完再執行,所以源碼中有一個計數的readyWait。

源碼中定義了一個等待棧變量——readyWait,每次執行$.holdReady(true)都會增加壓棧,而每次$.holdReady()執行都會彈棧,等空棧的時候就執行jQuery.ready函數,即將promise給resolve掉。

// Is the DOM ready to be used? Set to true once it occurs.
isReady: false,
// A counter to track how many items to wait for before
// the ready event fires. See #6781
readyWait: 1,

//第二步看這里
//holdReady推遲DOM的觸發
// Hold (or release) the ready event
holdReady: function( hold ) {
    if ( hold ) {
        jQuery.readyWait++;//hold為真,讓readyWait加加處理
    } else {
        jQuery.ready( true );
    }
},

// Handle when the DOM is ready
ready: function( wait ) { //參數和holdReady有關

    // Abort if there are pending holds or we're already ready
    //readyWait為0時就不用hold了,執行后面的操作
    if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { 
        return;
    }

    // Remember that the DOM is ready
    jQuery.isReady = true; //默認是false

    // If a normal DOM Ready event fired, decrement, and wait if need be
    if ( wait !== true && --jQuery.readyWait > 0 ) {
        return;
    }
    //第一步看這里重點,resolveWith改變狀態的時候傳參了,給done中方法fn傳入了參數,
    //document是fn的this指向,jQuery是參數
    // If there are functions bound, to execute
    readyList.resolveWith( document, [ jQuery ] );
    //跟主動觸發有關
    // Trigger any bound ready events
    if ( jQuery.fn.trigger ) {
        jQuery( document ).trigger("ready").off("ready");
    }
},

 

剛開始看源碼,很多地方理解也不到位,解釋可能也不清楚。

 

參考:

http://www.cnblogs.com/aeexiaoqiang/p/6525702.html

 

本文作者starof,因知識本身在變化,作者也在不斷學習成長,文章內容也不定時更新,為避免誤導讀者,方便追根溯源,請諸位轉載注明出處:http://www.cnblogs.com/starof/p/6856572.html有問題歡迎與我討論,共同進步。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM