1. 程式人生 > >【jQuery原始碼淺析】(五)--文件載入--$.ready

【jQuery原始碼淺析】(五)--文件載入--$.ready

前言

我們經常會用到$(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; };

思路分析

  1. 基本思想:實現文件載入完畢而不是所有資源載入完畢後立刻執行的函式。
  2. 簡便寫法:實現$(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()結果如下:
$().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的介紹將會在後續文章中進行闡述。

相關資料