分享

YY in Limbo 混沌海狂想 ? DOM加载事件的终极解决方案

 chanvy 2008-11-27

今天被土豆网的lex鄙视了,因为我的BLOG在Safari里根本无法浏览——loading永远不会结束,很明显,这意味着webkit引擎不支持上次提到的判断DOM加载完成的方法。

既然开了新文章,就干脆重新回顾一下这个问题:如今的javascript编程非常依赖DOM(文档对象模型),对HTML和XML来说,DOM是一个应用程序接口 (API) ,对JS来说,DOM为文档创建了程序可以使用的对象和方法,DOM把前端程序和内容结合在一起,就好像ORM(对象-关系映射,比如PEAR库里的DB_DataObject)把后台程序和关系数据库结合在一起,形象点说它是一颗节点树,没有这棵树的支撑,很多JS方法就无法使用。

传统方式是通过window.onload事件来判断DOM是否加载完成,但实际上它的加载时间里还包括了img标签插入的图片、FLASH等大家伙,很多时候,来不及等这些东西都下载完成才执行JS(特别是当你要用JS给页面元素注册事件、调整外观,用AJAX加载重要内容)。所以必须有一种方法来单独判断DOM是否加载完成。

国外很早就有强者解决了这个问题,但我上次看的资料其实不完整(不求甚解的下场),最终的解决方案是在几个人的BLOG上讨论出来的……blogsphere(博客圈)果然是一种能激发创造性的社区结构……

首先是Dean Edwards指出了两件重要的工具:

  • Mozilla提供一个很棒的事件:DOMContentLoaded
  • 微软支持defer属性,这并不是私有属性,而是包括在W3C的DOM 1标准里的,但是它似乎没有被XHTML支持,还有一个特点是,defer只能放在script标签里,而不能用JS来添加。

那么首先在Firefox上判断DOM加载完成就很简单了:

  1. if (document.addEventListener) 
  2. {
  3.     document.addEventListener("DOMContentLoaded", init, false);
  4. }

Opera9也支持上面的方法,但更低版本就不行了……

至于IE,最早提出的方法是把init函数放在一个单独的ie_onload.js里执行,在HTML调用脚本时加入条件注释和defer:

  1. <!--[if IE]><script defer src="ie_onload.js"></script><![endif]-->

或者:

  1. <!--[if IE]><script defer src="javascript:'init()'"></script><![endif]-->

这个方法的缺点是要在HTML里嵌入代码,缺乏灵活性,在Matthias Miller加入讨论后,产生了更方便的实现方法:

  1. // for Internet Explorer
  2.       /*@cc_on @*/
  3.       /*@if (@_win32)
  4.           document.write("<scr"+"ipt id=__ie_onload defer src=//0><\/scr"+"ipt>");
  5.           var script = document.getElementById("__ie_onload");
  6.           script.onreadystatechange = function() {
  7.               if (this.readyState == "complete") {
  8.                   init(); // call the onload handler
  9.               }
  10.           };
  11.       /*@end @*/

这里的cc就是conditional compilation(条件编译),跟条件注释很像,具体介绍可以看这里翻译的文章。这里使用CC是因为内部的代码会引起非IE浏览器的错误(比如Safari会出错)

而“script”被拆成”scr”+”ipt”似乎是为了避免与诺顿(Norton Internet Security)发生冲突-_____-b

如果你以为这样就可以应付所有情况了,就会像我一样被Safari用户鄙视……

jQuery的创始人John Resig在邮件列表里给出了Safari的解决方法:

  1. // for Safari
  2.       if (/WebKit/i.test(navigator.userAgent)) { // sniff
  3.           window.__load_timer = setInterval(function() {
  4.               if (/loaded|complete/.test(document.readyState)) {
  5.                   init(); // call the onload handler
  6.               }
  7.           }, 10);
  8.       }

最后有人封装了完整的代码:

  1. /*
  2. * (c)2006 Dean Edwards/Matthias Miller/John Resig
  3. * Special thanks to Dan Webb's domready.js Prototype extension
  4. * and Simon Willison's addLoadEvent
  5. *
  6. * For more info, see:
  7. * http://dean./weblog/2006/06/again/
  8. * http://www./bollocks/2006/06/21/a-dom-ready-extension-for-prototype
  9. * http://simon./archive/2004/05/26/addLoadEvent
  10. *
  11. * Thrown together by Jesse Skinner (http://www./)
  12. *
  13. *
  14. * To use: call addDOMLoadEvent one or more times with functions, ie:
  15. *
  16. *    function something() {
  17. *       // do something
  18. *    }
  19. *    addDOMLoadEvent(something);
  20. *
  21. *    addDOMLoadEvent(function() {
  22. *        // do other stuff
  23. *    });
  24. *
  25. */
  26.  
  27. function addDOMLoadEvent(func) {
  28.    if (!window.__load_events) {
  29.       var init = function () {
  30.           // quit if this function has already been called
  31.           if (arguments.callee.done) return;
  32.      
  33.           // flag this function so we don't do the same thing twice
  34.           arguments.callee.done = true;
  35.      
  36.           // kill the timer
  37.           if (window.__load_timer) {
  38.               clearInterval(window.__load_timer);
  39.               window.__load_timer = null;
  40.           }
  41.          
  42.           // execute each function in the stack in the order they were added
  43.           for (var i=0;i < window.__load_events.length;i++) {
  44.               window.__load_events[i]();
  45.           }
  46.           window.__load_events = null;
  47.       };
  48.   
  49.       // for Mozilla/Opera9
  50.       if (document.addEventListener) {
  51.           document.addEventListener("DOMContentLoaded", init, false);
  52.       }
  53.      
  54.       // for Internet Explorer
  55.       /*@cc_on @*/
  56.       /*@if (@_win32)
  57.           document.write("<scr"+"ipt id=__ie_onload defer src=//0><\/scr"+"ipt>");
  58.           var script = document.getElementById("__ie_onload");
  59.           script.onreadystatechange = function() {
  60.               if (this.readyState == "complete") {
  61.                   init(); // call the onload handler
  62.               }
  63.           };
  64.       /*@end @*/
  65.      
  66.       // for Safari
  67.       if (/WebKit/i.test(navigator.userAgent)) { // sniff
  68.           window.__load_timer = setInterval(function() {
  69.               if (/loaded|complete/.test(document.readyState)) {
  70.                   init(); // call the onload handler
  71.               }
  72.           }, 10);
  73.       }
  74.      
  75.       // for other browsers
  76.       window.onload = init;
  77.      
  78.       // create event function stack
  79.       window.__load_events = [];
  80.    }
  81.   
  82.    // add function to event stack
  83.    window.__load_events.push(func);
  84. }

而且他还结合了addLoadEvent函数,我在上篇文章里有一种往onload里注册新事件的简单方法,是利用JS的特性:“函数是一等公民”,而这里是利用数组来登记所有事件,与Ajax in Action里提到的Observer模式差不多。有了这段代码,只要使用addDOMLoadEvent(函数名),就可以反复添加事件函数,而不用担心覆盖原来注册的函数。

另外,喜欢用prototype的话,也可以用这个扩展

Matthias Miller还提到了HTTS加密连接的情况

国外BLOG上的好东西很多,我觉得想解决技术问题,与其买大部头或畅销书啃,不如用好搜索引擎,多在BLOG之间跳转几下,多看看评论,可以学到更多东西。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多