今天被土豆网的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.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里注册新事件的简单方法,是利用JS的特性:“函数是一等公民”,而这里是利用数组来登记所有事件,与Ajax in Action里提到的Observer模式差不多。有了这段代码,只要使用addDOMLoadEvent(函数名),就可以反复添加事件函数,而不用担心覆盖原来注册的函数。
另外,喜欢用prototype的话,也可以用这个扩展。
Matthias Miller还提到了HTTS加密连接的情况。
国外BLOG上的好东西很多,我觉得想解决技术问题,与其买大部头或畅销书啃,不如用好搜索引擎,多在BLOG之间跳转几下,多看看评论,可以学到更多东西。
很好的文章,看了半天终于大体明白了,一直习惯于window.onload应付了事,惭愧。Safari现在还没有时间去顾及,光是IE和Firefox就搞得我狼狈不堪了。
你的blog很有意思^_^
回复
**在想jQuery的$(function(){})是否支持所有主流浏览器**
回复
不知LZ有否接触EXT
其onReady()的方法大概就是实现这个功能了
回复
我知道YUI-EXT,不过库里的东西毕竟不能完全替代独立的方法……特别是EXT那么庞大的东西……
上文里提到的Dean Edwards也发布了自己的JS库,最近还有篇文章Rules For JavaScript Library Authors很有意思的。
回复
我没有 bs 过你,是 aether 假传圣旨。
loading 画面已经能自动隐藏了,但是隐藏得太快了,还没看完上面的句子。
但是在 Safari 里页面显示还是有些问题,换 WebKit 才一切正常。
回复
我是来看看同事们的,这么多人在!
回复
[…] 熟悉 jQuery 的朋友知道 $(document).ready 的好处就在这里,它只等待 DOM 加载完成,而不是页面。不过我们不可能做任何应用都引入略显庞大的 jQuery 框架,所以就有国外的大牛写了这个压缩后只有 617 字节的东西:addDOMLoadEvent。本来想亲自写一大段话解释解释这个东东,却 Google 出了别人早写好的,简体中文,有兴趣的直接跳转过去看吧。 […]
回复
请问 如何在webkit浏览器中解析一个xml文件啊
xmlDoc= document.implementation.createDocument(”",”",null);
opt.innerHTML=”sss”;
xmlDoc.load(”tree.xml”); // 问题 :在webkit中好像不能够load
xmlDoc.onload=getmessage;
回复