今天被土豆网的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加载完成就很简单了:
- if (document.addEventListener)
- {
- document.addEventListener("DOMContentLoaded", init, false);
- }
Opera9也支持上面的方法,但更低版本就不行了……
至于IE,最早提出的方法是把init函数放在一个单独的ie_onload.js里执行,在HTML调用脚本时加入条件注释和defer:
- <!--[if IE]><script defer src="ie_onload.js"></script><![endif]-->
或者:
- <!--[if IE]><script defer src="javascript:'init()'"></script><![endif]-->
这个方法的缺点是要在HTML里嵌入代码,缺乏灵活性,在Matthias Miller加入讨论后,产生了更方便的实现方法:
- // 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是因为内部的代码会引起非IE浏览器的错误(比如Safari会出错)
而“script”被拆成”scr”+”ipt”似乎是为了避免与诺顿(Norton Internet Security)发生冲突-_____-b
如果你以为这样就可以应付所有情况了,就会像我一样被Safari用户鄙视……
jQuery的创始人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./weblog/2006/06/again/
- * http://www./bollocks/2006/06/21/a-dom-ready-extension-for-prototype
- * http://simon./archive/2004/05/26/addLoadEvent
- *
- * Thrown together by Jesse Skinner (http://www./)
- *
- *
- * 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里注册新事件的简单方法,是利用JS的特性:“函数是一等公民”,而这里是利用数组来登记所有事件,与Ajax in Action里提到的Observer模式差不多。有了这段代码,只要使用addDOMLoadEvent(函数名),就可以反复添加事件函数,而不用担心覆盖原来注册的函数。
另外,喜欢用prototype的话,也可以用这个扩展。
Matthias Miller还提到了HTTS加密连接的情况。
国外BLOG上的好东西很多,我觉得想解决技术问题,与其买大部头或畅销书啃,不如用好搜索引擎,多在BLOG之间跳转几下,多看看评论,可以学到更多东西。