jQuery ready方法實現

很早之前就留下了這個問題,趕上五一放假,好好研究總結一下吧。

  1. 首先jq中ready方法和window的onload方法的不同這裏再提一下,首先ready只是dom樹加載完畢,一些img等資源可能還沒加載完成,而onload則是全部加載成功。而且ready方法可以有多個,而onload只能寫一個。還有一個區別是什麼了,想起來補上。

  2. 一般讓我們手寫模擬一個jq的ready方法,我大多都是這樣寫:

     document.ready = function (callback) {
            ///兼容FF,Google
            if (document.addEventListener) {
                        document.addEventListener('DOMContentLoaded', function () {
                            document.removeEventListener('DOMContentLoaded', arguments.callee, false);
                            callback();
                        }, false)
                    }
             //兼容IE
            else if (document.attachEvent) {
                document.attachEvent('onreadystatechange', function () {
                      if (document.readyState == "complete") {
                                document.detachEvent("onreadystatechange", arguments.callee);
                                callback();
                       }
                })
            }
            else if (document.lastChild == document.body) {
                callback();
            }
        }

先檢測DOMContentLoaded事件,然後撤銷綁定,在觸發回調,IE的就是檢測onreadystatechange事件,如果document.readyState == “complete”就老樣子先撤銷綁定事件,最後觸發回調,最後就是一個退化操作。

  1. ready方法出現的原因就是window.onload事件是在頁面所有的資源都加載完畢後觸發的. 如果頁面上有大圖片等資源響應緩慢, 會導致window.onload事件遲遲無法觸發.所以出現了DOM Ready事件. 此事件在DOM文檔結構準備完畢後觸發, 即在資源加載前觸發.

  2. 一般在主流瀏覽器,DOMContentLoaded 事件在許多Webkit瀏覽器以及IE9上都可以使用, 此事件會在DOM文檔準備好以後觸發, 包含在HTML5標準中. 對於支持此事件的瀏覽器, 直接使用DOMContentLoaded事件是最簡單最好的選擇.但是IE6,7,8都不支持DOMContentLoaded事件。

這裏寫圖片描述

在上面代碼中也就是這樣寫的,第一檢測的還是DOMContentLoaded事件,而且老版本瀏覽器或者IE6-8中,hack的方法就不知一種了:

  • 上面也寫到的readyState: 如果瀏覽器存在 document.onreadystatechange 事件,當該事件觸發時,如果 document.readyState=complete 的時候,可視爲 DOM 樹已經載入。
    eg:
    //onreadystatechange event
    document.onreadystatechange = function(e){
        document.getElementById("divMsg").innerHTML += "<br/> onreadystatechange, readyState:" + document.readyState;

    };
  • doScroll檢測:IE瀏覽器文檔中說明,當頁面 DOM 未加載完成時,調用 doScroll 方法時,會產生異常。(微軟的文檔指出doScroll必須在DOM主文檔準備完畢時纔可以正常觸發)那麼我們反過來用,如果不異常,那麼就是頁面DOM加載完畢了,可以不斷地通過能否執行 doScroll 判斷 DOM 是否加載完畢
    //doScroll
    var doScrollMoniterId = null;
    var doScrollMoniter = function(){
        try{
            document.documentElement.doScroll("left");
            document.getElementById("divMsg").innerHTML += "<br/>doScroll, readyState:" + document.readyState;
            if(doScrollMoniterId){
                clearInterval(doScrollMoniterId);
            }
        }
        catch(ex){
        }
    }
    doScrollMoniterId = setInterval(doScrollMoniter, 1);
  • setTimeout : 在setTimeout中觸發的函數, 一定會在DOM準備完畢後觸發.
var setTimeoutReady = function(){
    document.getElementById("divMsg").innerHTML += "<br/> setTimeout , readyState:" + document.readyState;
};
var setTimeoutBindReady = function(){
    /in/.test(document.readyState)?setTimeout(arguments.callee, 1):setTimeoutReady();
};
setTimeoutBindReady();
  • 外部script: 通過設置了script塊的defer屬性實現.詳見:鏈接
  • 內部script: 外部script的改進版本. 外部script需要頁面引用額外的js文件. 內部script方法可以避免此問題。詳見:鏈接

上面的一些方法其實也存在問題,比如readyState狀態爲complete的時候圖片已經加載完了。
所以引自一個大佬的說法就是:
這裏寫圖片描述

具體使用doScroll的話上次看到了這種寫法:

//爲了保證最後一定會調用ready方法,在上面每一種方式的最後都還是會爲load事件綁定ready方法。
if ( document.readyState === "complete" ) {
      // Handle it asynchronously to allow scripts the opportunity to delay ready
      //這裏的setTimeout是爲了異步
      setTimeout( jQuery.ready, 1 );

    // Standards-based browsers support DOMContentLoaded
    //標準瀏覽器偵聽事件接口:document.addEventListener
    } else if ( document.addEventListener ) {
      // Use the handy event callback
      document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );

      // A fallback to window.onload, that will always work
      //文章一開始說了,這裏爲了保證一定會觸發ready,所以還針對onload也綁定一次回調
      window.addEventListener( "load", jQuery.ready, false );

    // If IE event model is used
    } else {
      //IE偵聽事件接口:document.attachEvent
      //如果有onreadystatechange事件,偵聽之
      // Ensure firing before onload, maybe late but safe also for iframes
      document.attachEvent( "onreadystatechange", DOMContentLoaded );

      // A fallback to window.onload, that will always work
      window.attachEvent( "onload", jQuery.ready );


      // http://javascript.nwbox.com/IEContentLoaded/
      // 見下邊說明
      // If IE and not a frame
      // continually check to see if the document is ready
      var top = false;

      try {
        top = window.frameElement == null && document.documentElement;
      } catch(e) {}

      //如果是IE並且不是iframe
      if ( top && top.doScroll ) {
        (function doScrollCheck() {
          if ( !jQuery.isReady ) {

            try {
              // Use the trick by Diego Perini
              // http://javascript.nwbox.com/IEContentLoaded/
              //一直調用doScroll滾動,因爲DOM渲染結束前,DOM節點是沒有doScroll方法的,所以一直會異常
              //直到DOM渲染結束了,這個時候doScroll方法不會拋出異常,然後就調用$.ready()
              top.doScroll("left");
            } catch(e) {
              return setTimeout( doScrollCheck, 50 );
            }

            // and execute any waiting functions
            jQuery.ready();
          }
        })();
      }
    }

上面的代碼即實現這樣的流程:
這裏寫圖片描述

jQuery源碼的實現稍有複雜,等日後分析懂了繼續補充。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章