學習 jQuery 有許多途徑,咱們今天從 jQuery 的 ready 函數開始。本例中的代碼都來自於 jQuery 腳本庫。javascript
若是你使用過 jQuery , 就必然使用過 ready 函數,它用來註冊當頁面準備好以後能夠執行的函數。java
問題來啦,咱們的頁面何時準備好了呢?jquery
最基本的處理方式就是頁面的 onload 事件,咱們在處理這個事件的時候,能夠有多種方式,便可以經過 HTML 方式,直接寫在 body 元素的開始標記中,也可使用事件註冊的方式來使用,這又能夠分爲 DOM0 方式和 DOM2 方式。再考慮到瀏覽器的兼容性,使用 DOM2 方式寫出來,以下所示。promise
if (document.addEventListener) { // A fallback to window.onload, that will always work window.addEventListener("load", jQuery.ready, false); // If IE event model is used} else { // A fallback to window.onload, that will always work window.attachEvent("onload", jQuery.ready); }
不過 onload 事件要等到全部頁面元素加載完成纔會觸發, 包括頁面上的圖片等等。若是網頁上有大量的圖片,效果可想而知,用戶可能在沒有看到圖片的時候,就已經開始操做頁面了,而這時咱們的頁面尚未初始化,事件尚未註冊上,這豈不是太晚了!瀏覽器
除了你們熟知的 onload 事件以外, 與 DOM 中的 onload 事件相近的,咱們還有 DOMContentLoaded 事件能夠考慮, 基於標準的瀏覽器支持這個事件, 當全部 DOM 解析完之後會觸發這個事件。緩存
這樣,對於基於標準的瀏覽器來講,咱們還能夠註冊這個事件的處理。這樣,咱們可能更早地捕獲到加載完成的事件。app
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); }
不標準的瀏覽器怎麼辦呢?dom
若是瀏覽器存在 document.onreadystatechange 事件,當該事件觸發時,若是 document.readyState=complete 的時候,可視爲 DOM 樹已經載入。async
不過,這個事件不太可靠,好比當頁面中存在圖片的時候,可能反而在 onload 事件以後才能觸發,換言之,它只能正確地執行於頁面不包含二進制資源或很是少或者被緩存時做爲一個備選吧。函數
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 { // 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); }
DOMContentLoaded 函數在作什麼呢?最終仍是要調用 jQuery.ready 函數。
DOMContentLoaded = function() { if ( document.addEventListener ) { document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); jQuery.ready(); } else if ( document.readyState === "complete" ) { // we're here because readyState === "complete" in oldIE // which is good enough for us to call the dom ready! document.detachEvent( "onreadystatechange", DOMContentLoaded ); jQuery.ready(); } }
MSDN 關於 JScript 的一個方法有段不起眼的話,當頁面 DOM 未加載完成時,調用 doScroll 方法時,會產生異常。那麼咱們反過來用,若是不異常,那麼就是頁面DOM加載完畢了!
Diego Perini 在 2007 年的時候,報告了一種檢測 IE 是否加載完成的方式,使用 doScroll 方法調用。詳細的說明見這裏。
原理是對於 IE 在非 iframe 內時,只有不斷地經過可否執行 doScroll 判斷 DOM 是否加載完畢。在本例中每間隔 50 毫秒嘗試去執行 doScroll,注意,因爲頁面沒有加載完成的時候,調用 doScroll 會致使異常,因此使用了 try -catch 來捕獲異常。
(function doScrollCheck() { if (!jQuery.isReady) { try { // Use the trick by Diego Perini // http://javascript.nwbox.com/IEContentLoaded/ top.doScroll("left"); } catch (e) { return setTimeout(doScrollCheck, 50); } // and execute any waiting functions jQuery.ready(); } })();
若是咱們註冊 ready 函數的時間點太晚了,頁面已經加載完成以後,咱們才註冊本身的 ready 函數,那就用不着上面的層層檢查了,直接看看當前頁面的 readyState 就能夠了,若是已是 complete ,那就能夠直接執行咱們準備註冊的 ready 函數了。不過 ChrisS 報告了一個很特別的錯誤狀況,咱們須要延遲一下執行。
setTimeout 常常被用來作網頁上的定時器,容許爲它指定一個毫秒數做爲間隔執行的時間。當被啓動的程序須要在很是短的時間內運行,咱們就會給她指定一個很小的時間數,或者須要立刻執行的話,咱們甚至把這個毫秒數設置爲0,但事實上,setTimeout有一個最小執行時間,當指定的時間小於該時間時,瀏覽器會用最小容許的時間做爲setTimeout的時間間隔,也就是說即便咱們把setTimeout的毫秒數設置爲0,被調用的程序也沒有立刻啓動。
這個最小的時間間隔是多少呢?這和瀏覽器及操做系統有關。在John Resig的新書《Javascript忍者的祕密》一書中提到
Browsers all have a 10ms minimum delay on OSX and a(approximately) 15ms delay on Windows.(在蘋果機上的最小時間間隔是10毫秒,在Windows系統上的最小時間間隔大約是15毫秒)
,另外,MDC中關於setTimeout的介紹中也提到,Firefox中定義的最小時間間隔(DOM_MIN_TIMEOUT_VALUE)是10毫秒,HTML5定義的最小時間間隔是4毫秒。既然規範都是這樣寫的,那看來使用setTimeout是沒辦法再把這個最小時間間隔縮短了。
這樣,經過設置爲 1, 咱們可讓程序在瀏覽器支持的最小時間間隔以後執行了。
// Catch cases where $(document).ready() is called after the browser event has already occurred.// we once tried to use readyState "interactive" here, but it caused issues like the one// discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15if (document.readyState === "complete") { // 延遲 1 毫秒以後,執行 ready 函數 setTimeout(jQuery.ready, 1); }
在 jQuery 中完整的代碼以下所示。位於 jQuery 1.8.3 源代碼的 #842 行。
jQuery.ready.promise = function( obj ) { if ( !readyList ) { readyList = jQuery.Deferred(); // Catch cases where $(document).ready() is called after the browser event has already occurred. // we once tried to use readyState "interactive" here, but it caused issues like the one // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 if ( document.readyState === "complete" ) { // Handle it asynchronously to allow scripts the opportunity to delay ready setTimeout( jQuery.ready, 1 ); // Standards-based browsers support DOMContentLoaded } else 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 { // 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 top = false; try { top = window.frameElement == null && document.documentElement; } catch(e) {} if ( top && top.doScroll ) { (function doScrollCheck() { if ( !jQuery.isReady ) { try { // Use the trick by Diego Perini // http://javascript.nwbox.com/IEContentLoaded/ top.doScroll("left"); } catch(e) { return setTimeout( doScrollCheck, 50 ); } // and execute any waiting functions jQuery.ready(); } })(); } } } return readyList.promise( obj ); };
那麼,又是誰來調用呢?固然是須要的時候,在咱們調用 ready 函數的時候,才須要註冊這些判斷頁面是否徹底加載的處理,這段代碼在 1.8.3 中位於代碼的 #244 行,以下所示:
ready: function( fn ) { // Add the callback jQuery.ready.promise().done( fn ); return this; }
在頁面上引用 jQuery 腳本庫以後,執行了 jQuery 的初始化函數,初始化函數中建立了 ready 函數。咱們在經過 ready 函數註冊事件處理以前,jQuery 完成了頁面檢測代碼的註冊。這樣。當頁面徹底加載以後,咱們註冊的函數就被調用了。