![]() |
Ready . Set . Go |
.ready(handler)如果你剛好是那 0.1% 的人沒用,放心 jQuery 自己也會幫你呼叫 :) jQuery 自己會在一開始就先偷偷註冊兩個 onReady handler 去做 browser feature detection。
.ready 的用法有三種:
- $(document).ready(handler)
- $().ready(handler)
- $(handler)
// HANDLE: $(function) | |
// Shortcut for document ready | |
} else if ( jQuery.isFunction( selector ) ) { | |
return rootjQuery.ready( selector ); | |
} |
不過,以筆者的個人看法,第二個方法效能才是上等的建議啊,不然客倌您看:
init: function( selector, context, rootjQuery ) { | |
var match, elem, ret, doc; | |
// Handle $(""), $(null), or $(undefined) | |
if ( !selector ) { | |
return this; | |
} | |
// Handle $(DOMElement) | |
if ( selector.nodeType ) { | |
this.context = this[0] = selector; | |
this.length = 1; | |
return this; | |
} |
接著來看看 .ready 裡面做了什麼事情:
ready: function( fn ) { | |
// Attach the listeners | |
jQuery.bindReady(); | |
// Add the callback | |
readyList.add( fn ); | |
return this; | |
} | |
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(); | |
} | |
} | |
} | |
看得出來這邊的重點在於使用了 readyList 來追蹤所有的 onReady handler,而 readyList 是一個 jQuery.Callbacks 回傳的 object instance,傳入的參數是以空格分隔的, once memory 代表的意思是當 fire 過一次就失效。 Callbacks 基本上很類似一個 array 裡面存放 function references,同時加上一些 fire event 的處理。之後當收到 DOMContentLoaded event 的時候就可以透過這個 readyList 去 fire 所有已註冊的 on ready hanlders。
之後接著檢查如果已經是在 complete 的 readyState 下,那就直接觸發 jQuery.ready。
正常情況下則是去註冊 DOMContentLoaded 去觸發 jQuery.ready,然後後面再註冊 load event 以避免在某些瀏覽器沒有 DOMContentLoaded 的 support 的情況下整個程式就不會動了。 DOMContentLoaded 與 load 的差別在於 DOMContentLoaded 會在所有的 DOM nodes 都 ready 的時候觸發,而 load 則是必須在所有的 Node 的內容都 ready 才會觸發。比如說你的網頁中有放 <img>,那麼 DOMContentLoaded 觸發的時候 <img> 的圖片內容一定是還沒觸發,但 load 被觸發的時候一定是在 <img> 圖片已經完全載入的時候。所以如果你預計再圖片載入完之後去計算出新的 layout,那你就不能用 .ready 而要改用 .load。
另外一點值得提的是,也可以對 img element 註冊 load 事件,但是 load 無法處理瀏覽器 cached images,也就是說如果你預期每次都要收到 img 的 onload 事件來做一些運算去重新 layout 的話,一定會遇到很多問題,比如 Pinterest like 的 layout 就必須取得每一張圖片的大小來計算每一個 Pin 的高度,這時候單純依賴 load 就會有問題。因此有了一個對應的 Plugin - imageloaded 來幫忙處理 image 的 load event。
至於前面提到的 jQuery 會自己註冊的 feature detection 的 function 可以參考這個 gist: https://gist.github.com/3183078,裡面會做以下 features 的偵測:
偵測的技巧是值得學習的,原理大概是建立一個 DOM element,並加入 DOM tree,再重這個 DOM node 的屬性或是 computedStyle 去偵測 feature。相關的技巧應用的很完整的是 Modernizr,有興趣的話可以找一下 EricSK 的 Modernier 源碼剖析文章。