jQuery實現DOM加載方法源碼分析

傳統的判斷dom加載的方法

使用 dom0級 onload事件來進行觸發全部瀏覽器都支持在最初是很流行的寫法 咱們都熟悉這種寫法:javascript

window.onload=function(){ ... } 

 可是onload事件觸發過於緩慢,尤爲是在存在不少外部圖片或者視頻文件的時候,爲了更好的瞭解這一點有必要知道一個html文檔是如何進行加載的,這裏引用一個園友的表述:html

  1.用戶輸入網址(假設是個html頁面,而且是第一次訪問),瀏覽器向服務器發出請求,服務器返回html文件; 

  2.瀏覽器開始載入html代碼,發現<head>標籤內有一個<link>標籤引用外部CSS文件; 

  3.瀏覽器又發出CSS文件的請求,服務器返回這個CSS文件; 

  4.瀏覽器繼續載入html中<body>部分的代碼,而且CSS文件已經拿到手了,能夠開始渲染頁面了; 

  5.瀏覽器在代碼中發現一個<img>標籤引用了一張圖片,向服務器發出請求。此時瀏覽器不會等到圖片下載完,而是繼續渲染後面的代碼; 

  6.服務器返回圖片文件,因爲圖片佔用了必定面積,影響了後面段落的排布,所以瀏覽器須要回過頭來從新渲染這部分代碼; 

  7.瀏覽器發現了一個包含一行Javascript代碼的<script>標籤,趕快運行它; 

  8.Javascript腳本執行了這條語句,它命令瀏覽器隱藏掉代碼中的某個<div> (style.display=」none」)。杯具啊,忽然就少了這麼一個元素,瀏覽器不得不從新渲染這部分代碼; 

  9.終於等到了</html>的到來,瀏覽器淚流滿面…… 

  10.等等,還沒完,用戶點了一下界面中的「換膚」按鈕,Javascript讓瀏覽器換了一下<link>標籤的CSS路徑; 

  11.瀏覽器召集了在座的各位<div><span><ul><li>們,「大夥兒收拾收拾行李,咱得從新來過……」,瀏覽器向服務器請求了新的CSS文件,從新渲染頁面。java

能夠看到是先加載dom結構後加載對用的資源 好比一個一個img標籤  ,瀏覽器再加載img標籤時不會等到src對應的圖片加載完成就會執行後面的代碼,而onload則必需要等到全部資源加載完成纔會觸發,因此domContentLoaded 就代替了onload  可是對於ie低版本瀏覽器來講這種方法尚未實現 ,那麼如何實現完美的判斷dom加載呢?下面來看jquery的寫法:jquery

 

使用jquery進行開發

 

$(function(){
    ...
})    

//or

$(doucment).ready(function(){
    ... 
})

在稍後的分析中會發現二者並沒有區別,下面就從這個入口開始一步一步瞭解jquery的寫法:web

 

源碼分析

 

首先$(fn) 是在構造函數裏傳入了一個函數 在init函數(init?若是不瞭解jqurey構造函數能夠查看博主以前的文章http://www.cnblogs.com/yy-hh/p/4492887.html瀏覽器

// HANDLE: $(function) // Shortcut for document ready
        } else if ( jQuery.isFunction( selector ) ) { return rootjQuery.ready( selector ); }

若是傳入的是一個函數 則會執行 rootjQuery.ready( selector );  rootjQuery是什麼呢?緩存

// All jQuery objects should point back to these
rootjQuery = jQuery(document);

 

其實就是$(document) ,而後執行了一個原型方法ready把函數做爲參數傳了進去,好的如今視線轉移到ready(此方法是原型方法還有工具方法不要混淆)服務器

ready: function( fn ) { // Attach the listeners
 jQuery.bindReady(); // Add the callback
 readyList.add( fn ); return this; },

 

fn接受了傳遞進來的函數 先執行了一個工具方法bindReady,視線接着轉移框架

bindReady: function() { if ( readyList ) { return; } readyList = jQuery.Callbacks( "once memory" ); // Catch cases where $(document).ready() is called after the
        // browser event has already occurred.
        if ( document.readyState === "complete" ) { // Handle it asynchronously to allow scripts the opportunity to delay ready
            return setTimeout( jQuery.ready, 1 ); } // Mozilla, Opera and webkit nightlies currently support this event
        if ( document.addEventListener ) { // Use the handy event callback
            document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); // A fallback to window.onload, that will always work
            window.addEventListener( "load", jQuery.ready, false ); // If IE event model is used
        } else if ( document.attachEvent ) { // 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 ); // If IE and not a frame
            // continually check to see if the document is ready
            var toplevel = false; try { toplevel = window.frameElement == null; } catch(e) {} if ( document.documentElement.doScroll && toplevel ) { doScrollCheck(); } } },

 

這個方法看起來複雜,呵呵不要着急一行一行的看 咱們如今的分析路線是 $(fn)->$(document).ready->$.bindReadydom

     if ( readyList ) { return; }

 

這裏出現了一個新變量readyList  第一次執行的時候因爲只有聲明沒有初始化確定是undefined因此不會走這裏

    // The deferred used on DOM ready
    readyList,

 

readyList = jQuery.Callbacks( "once memory" );

 

而後給readyList賦值 其最爲成爲了一個回調對象 關於jquery回調對象的方法這裏再也不贅述,回調對象建立了可是目前是沒有添加回調方法的

  // Catch cases where $(document).ready() is called after the
 // browser event has already occurred.
        if ( document.readyState === "complete" ) { // Handle it asynchronously to allow scripts the opportunity to delay ready
            return setTimeout( jQuery.ready, 1 ); }
document.readyState表示文檔加載的狀態,若是加載完成了則能夠直接執行ready方法也是也就是執行傳遞的回調函數,既然已經記載好了就能夠直接執行了,使用settimeout是保證函數能夠異步加載

接來下來的事情就是用dom2級事件處理程序來監聽onload事件 和 domcontentLoaded 既而後者加載速度比前者快爲什嗎還要畫蛇添足呢?這是由於瀏覽器可能會緩存事件處理程序onload可能會被緩存而先執行因此都寫上誰先觸發誰先執行;
只不過對於domcontentLoaded是執行的domcontentLoaded方法而不是ready方法,其實domcontentLoaded方法也是最終執行ready方法 :
// Cleanup functions for the document ready method
if ( document.addEventListener ) { DOMContentLoaded = function() { document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); jQuery.ready(); }; } else if ( document.attachEvent ) { DOMContentLoaded = function() { // Make sure body exi msts, at least, in case IE gets a little overzealous (ticket #5443).
        if ( document.readyState === "complete" ) { document.detachEvent( "onreadystatechange", DOMContentLoaded ); jQuery.ready(); } }; }

 

只不過是先解除綁定以後再執行確保不會屢次觸發,對於ie瀏覽器還有一個特殊的方法就是檢測滾動條是能夠能夠執行 固然前提不在框架頁面 ,由於若是dom結構加載好了body纔有滾動條 

 if ( document.documentElement.doScroll && toplevel ) { doScrollCheck(); }

 

// The DOM ready check for Internet Explorer
function doScrollCheck() { if ( jQuery.isReady ) { return; } try { // If IE is used, use the trick by Diego Perini
        // http://javascript.nwbox.com/IEContentLoaded/
        document.documentElement.doScroll("left"); } catch(e) { setTimeout( doScrollCheck, 1 ); return; } // and execute any waiting functions
 jQuery.ready(); }

isReady是判斷是否已經加載的狀態值 只有執行ready工具方法後纔會變成true,而後就是不停的檢測滾動條 直不報錯了執行ready方法;

因此bindReady方法就是一個準備方法,把要執行的函數綁定在回調函數中而且判斷什麼時候才能去觸發,最終都執行$.ready方法 注意這裏的ready是工具方法 不一樣於上面說的ready原型方法或者叫實例方法

立刻就能夠看到函數被觸發了可是彆着急 尚未把傳進來的fn添加到回調函數列表中呢,看完bindReady以後咱們再回到ready實例方法中

 

ready: function( fn ) { // Attach the listeners
 jQuery.bindReady(); // Add the callback
 readyList.add( fn ); return this; },

 

原來是在這裏添加的 因爲bindReady中調用jQuery.ready時都是採用的異步因此徹底添加操做得以在執行以前完成 ,如今能夠看最後工具方法ready了吧?固然不是你還要直到另外一個方法holdReady

    // Hold (or release) the ready event
    holdReady: function( hold ) { if ( hold ) { jQuery.readyWait++; } else { jQuery.ready( true ); } },

 

代碼很少主要就是阻止回調函數觸發的,好比咱們在代碼中間須要加載一個腳本文件而且但願優先於rady事件執行就可使用此方法先中止執行後再恢復實現動態腳本加載參數爲false若是不傳就是組織ready事件若是傳入就是解除阻止,準備工做終於完成下面開始看jQuery.ready方法:

    // Handle when the DOM is ready
    ready: function( wait ) { // Either a released hold or an DOMready/load event and not yet ready
        if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) { // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
            if ( !document.body ) { return setTimeout( jQuery.ready, 1 ); } 

 

此方法接受一個參數也就是holdReady可能傳入的true 這裏限制了兩個條件才能繼續運行 1,wait爲true readyWait減一後爲0,readyWait是一個計數器,由於holdReady能夠執行屢次,沒執行一次該值加一解除一次該值減一   2,wait不爲true 而且isRead爲false 由於isReady只用執行到這條if語句後面才能修改成ture因此這是保證不要重複執行的 。正常狀況下(沒有調用holdReady)都是能夠經過的,若是調用了而且wait存在說明有解除可是若是解除次數低於阻止次數仍是不行的;

if進來以後又是一個if判斷這裏是這對ie的一個bug能夠忽視 有興趣查看jQuery官網說明http://bugs.jquery.com/ticket/5443 下面就可讓isReady爲true了

// 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; }

 

ready狀態改變以後並不意味着能夠馬上執行回調函數了,在前面判斷了沒有使用holdReady以及使用了holdReady(false)的狀況 這兩種狀況僅僅能夠知足isReady爲ture  可是若是使用了holdReady沒有傳值的狀況時只要readyWait減一後大於0仍是不能執行可是下次解除時isReady狀態已是true了

// If there are functions bound, to execute
 readyList.fireWith( document, [ jQuery ] ); // Trigger any bound ready events
            if ( jQuery.fn.trigger ) { jQuery( document ).trigger( "ready" ).off( "ready" ); }

 

最終建立的回調對象經過fireWith方法執行了,而且把this指向了doument而且把jQuery做爲參數傳遞了進去 最後針對有可能使用 on方法綁定ready事件也進行了trigger觸發而後解除綁定;至此完畢 機構比較複雜須要看着源碼多理幾回,最後貼上主要源碼

 
 

//
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, // Hold (or release) the ready event holdReady: function( hold ) { if ( hold ) { jQuery.readyWait++; } else { jQuery.ready( true ); } }, // Handle when the DOM is ready ready: function( wait ) { // Either a released hold or an DOMready/load event and not yet ready if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) { // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). if ( !document.body ) { return setTimeout( jQuery.ready, 1 ); } // 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; } // If there are functions bound, to execute readyList.fireWith( document, [ jQuery ] ); // Trigger any bound ready events if ( jQuery.fn.trigger ) { jQuery( document ).trigger( "ready" ).off( "ready" ); } } }, bindReady: function() { if ( readyList ) { return; } readyList = jQuery.Callbacks( "once memory" ); // if ( document.readyState === "complete" ) { // Handle it asynchronously to allow scripts the opportunity to delay ready return setTimeout( jQuery.ready, 1 ); } // Mozilla, Opera and webkit nightlies currently support this event if ( document.addEventListener ) { // Use the handy event callback document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); // A fallback to window.onload, that will always work window.addEventListener( "load", jQuery.ready, false ); // If IE event model is used } else if ( document.attachEvent ) { // 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 ); // If IE and not a frame // continually check to see if the document is ready var toplevel = false; try { toplevel = window.frameElement == null; } catch(e) {} if ( document.documentElement.doScroll && toplevel ) { doScrollCheck(); } } },
相關文章
相關標籤/搜索