![]() |
Ready . Set . Go |
.ready(handler)如果你剛好是那 0.1% 的人沒用,放心 jQuery 自己也會幫你呼叫 :) jQuery 自己會在一開始就先偷偷註冊兩個 onReady handler 去做 browser feature detection。
.ready 的用法有三種:
- $(document).ready(handler)
- $().ready(handler)
- $(handler)
前兩個方法,如 jQuery 原始碼解讀 (II) 所講的會去呼叫 jQuery.fn.init 然後以傳入的第一個參數當作 selector,由於 document 本身就是一個 DOMNode 所以直接就是回傳 context 為 document 的 jQuery object instance,而 $() 則是回傳一個空的 context 的 jQuery object instance,但因為 .ready 其實跟 jQuery object 本身綁定的 context 無關,所以其實你傳什麼 selector 進去都可以直接呼叫 .ready 。但是除了 document 或是 null/""/undefined 之外的 selector 在 ready 前其實都不會 work,所以基本上就只能 $(document) 或是 $()。
至於第三個方法,其實只是 jQuery 故意留一個 shortcut,但感覺好像也沒什麼必要
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// HANDLE: $(function) | |
// Shortcut for document ready | |
} else if ( jQuery.isFunction( selector ) ) { | |
return rootjQuery.ready( selector ); | |
} |
而且這個 shortcut 的 condition check branch 是在最後一段,所以其實效能是最差的,但是 jQuery 官方文件反而是建議不要使用第二個方法?有點不解。綜合 jQuery 文件跟筆者小小的看法,我們還是用第一個好了 :)
不過,以筆者的個人看法,第二個方法效能才是上等的建議啊,不然客倌您看:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 裡面做了什麼事情:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 源碼剖析文章。
jQuery 系列文章:
沒有留言:
張貼留言