今天被土豆網(wǎng)的lex鄙視了,因為我的BLOG在Safari里根本無(wú)法瀏覽——loading永遠不會(huì )結束,很明顯,這意味著(zhù)webkit引擎不支持上次提到的判斷DOM加載完成的方法。
既然開(kāi)了新文章,就干脆重新回顧一下這個(gè)問(wèn)題:如今的javascript編程非常依賴(lài)DOM(文檔對象模型),對HTML和XML來(lái)說(shuō),DOM是一個(gè)應用程序接口 (API) ,對JS來(lái)說(shuō),DOM為文檔創(chuàng )建了程序可以使用的對象和方法,DOM把前端程序和內容結合在一起,就好像ORM(對象-關(guān)系映射,比如PEAR庫里的DB_DataObject)把后臺程序和關(guān)系數據庫結合在一起,形象點(diǎn)說(shuō)它是一顆節點(diǎn)樹(shù),沒(méi)有這棵樹(shù)的支撐,很多JS方法就無(wú)法使用。
傳統方式是通過(guò)window.onload事件來(lái)判斷DOM是否加載完成,但實(shí)際上它的加載時(shí)間里還包括了img標簽插入的圖片、FLASH等大家伙,很多時(shí)候,來(lái)不及等這些東西都下載完成才執行JS(特別是當你要用JS給頁(yè)面元素注冊事件、調整外觀(guān),用AJAX加載重要內容)。所以必須有一種方法來(lái)單獨判斷DOM是否加載完成。
國外很早就有強者解決了這個(gè)問(wèn)題,但我上次看的資料其實(shí)不完整(不求甚解的下場(chǎng)),最終的解決方案是在幾個(gè)人的BLOG上討論出來(lái)的……blogsphere(博客圈)果然是一種能激發(fā)創(chuàng )造性的社區結構……
首先是Dean Edwards指出了兩件重要的工具:
- Mozilla提供一個(gè)很棒的事件:DOMContentLoaded
- 微軟支持defer屬性,這并不是私有屬性,而是包括在W3C的DOM 1標準里的,但是它似乎沒(méi)有被XHTML支持,還有一個(gè)特點(diǎn)是,defer只能放在script標簽里,而不能用JS來(lái)添加。
那么首先在Firefox上判斷DOM加載完成就很簡(jiǎn)單了:
- if (document.addEventListener)
- {
- document.addEventListener("DOMContentLoaded", init, false);
- }
Opera9也支持上面的方法,但更低版本就不行了……
至于IE,最早提出的方法是把init函數放在一個(gè)單獨的ie_onload.js里執行,在HTML調用腳本時(shí)加入條件注釋和defer:
- <!--[if IE]><script defer src="ie_onload.js"></script><![endif]-->
或者:
- <!--[if IE]><script defer src="javascript:'init()'"></script><![endif]-->
這個(gè)方法的缺點(diǎn)是要在HTML里嵌入代碼,缺乏靈活性,在Matthias Miller加入討論后,產(chǎn)生了更方便的實(shí)現方法:
- // for Internet Explorer
- /*@cc_on @*/
- /*@if (@_win32)
- document.write("<scr"+"ipt id=__ie_onload defer src=//0><\/scr"+"ipt>");
- var script = document.getElementById("__ie_onload");
- script.onreadystatechange = function() {
- if (this.readyState == "complete") {
- init(); // call the onload handler
- }
- };
- /*@end @*/
這里的cc就是conditional compilation(條件編譯),跟條件注釋很像,具體介紹可以看這里翻譯的文章。這里使用CC是因為內部的代碼會(huì )引起非IE瀏覽器的錯誤(比如Safari會(huì )出錯)
而“script”被拆成”scr”+”ipt”似乎是為了避免與諾頓(Norton Internet Security)發(fā)生沖突-_____-b
如果你以為這樣就可以應付所有情況了,就會(huì )像我一樣被Safari用戶(hù)鄙視……
jQuery的創(chuàng )始人John Resig在郵件列表里給出了Safari的解決方法:
- // for Safari
- if (/WebKit/i.test(navigator.userAgent)) { // sniff
- window.__load_timer = setInterval(function() {
- if (/loaded|complete/.test(document.readyState)) {
- init(); // call the onload handler
- }
- }, 10);
- }
最后有人封裝了完整的代碼:
- /*
- * (c)2006 Dean Edwards/Matthias Miller/John Resig
- * Special thanks to Dan Webb's domready.js Prototype extension
- * and Simon Willison's addLoadEvent
- *
- * For more info, see:
- * http://dean.edwards.name/weblog/2006/06/again/
- * http://www.vivabit.com/bollocks/2006/06/21/a-dom-ready-extension-for-prototype
- * http://simon.incutio.com/archive/2004/05/26/addLoadEvent
- *
- * Thrown together by Jesse Skinner (http://www.thefutureoftheweb.com/)
- *
- *
- * To use: call addDOMLoadEvent one or more times with functions, ie:
- *
- * function something() {
- * // do something
- * }
- * addDOMLoadEvent(something);
- *
- * addDOMLoadEvent(function() {
- * // do other stuff
- * });
- *
- */
- function addDOMLoadEvent(func) {
- if (!window.__load_events) {
- var init = function () {
- // quit if this function has already been called
- if (arguments.callee.done) return;
- // flag this function so we don't do the same thing twice
- arguments.callee.done = true;
- // kill the timer
- if (window.__load_timer) {
- clearInterval(window.__load_timer);
- window.__load_timer = null;
- }
- // execute each function in the stack in the order they were added
- for (var i=0;i < window.__load_events.length;i++) {
- window.__load_events[i]();
- }
- window.__load_events = null;
- };
- // for Mozilla/Opera9
- if (document.addEventListener) {
- document.addEventListener("DOMContentLoaded", init, false);
- }
- // for Internet Explorer
- /*@cc_on @*/
- /*@if (@_win32)
- document.write("<scr"+"ipt id=__ie_onload defer src=//0><\/scr"+"ipt>");
- var script = document.getElementById("__ie_onload");
- script.onreadystatechange = function() {
- if (this.readyState == "complete") {
- init(); // call the onload handler
- }
- };
- /*@end @*/
- // for Safari
- if (/WebKit/i.test(navigator.userAgent)) { // sniff
- window.__load_timer = setInterval(function() {
- if (/loaded|complete/.test(document.readyState)) {
- init(); // call the onload handler
- }
- }, 10);
- }
- // for other browsers
- window.onload = init;
- // create event function stack
- window.__load_events = [];
- }
- // add function to event stack
- window.__load_events.push(func);
- }
而且他還結合了addLoadEvent函數,我在上篇文章里有一種往onload里注冊新事件的簡(jiǎn)單方法,是利用JS的特性:“函數是一等公民”,而這里是利用數組來(lái)登記所有事件,與Ajax in Action里提到的Observer模式差不多。有了這段代碼,只要使用addDOMLoadEvent(函數名),就可以反復添加事件函數,而不用擔心覆蓋原來(lái)注冊的函數。
另外,喜歡用prototype的話(huà),也可以用這個(gè)擴展。
Matthias Miller還提到了HTTS加密連接的情況。
國外BLOG上的好東西很多,我覺(jué)得想解決技術(shù)問(wèn)題,與其買(mǎi)大部頭或暢銷(xiāo)書(shū)啃,不如用好搜索引擎,多在BLOG之間跳轉幾下,多看看評論,可以學(xué)到更多東西。

