西风坊

哦,犷野西风,你秋之实体的气息……

« 获取元素在页面中的位置速度,还是速度! »

window.onload || Dom ready

相信但凡web开发人员都知道window.onload的意思,在页面加载完毕的事件,通常我们的js代码总是需要有对应的html结构去操作的。比如熟知的getElementById,必须在id存在的情况下才能调用,否则就会报错。于是在js文件上写一个window.onload=function(){//code}几乎是最常用的操作了。然而,onload事件却是要等到页面上的图片等信息全部加载完毕之后才执行的,大家知道大量的图片加载时非常耗时间的,在等待突破加载的过程中,DOM树很可能早已经加载完毕了,各种基于DOM的操作已经可以正常的进行了。还要等,显然是非常不合时宜的。

前段时间好好学习了yahoo提供给我们的有关页面性能方面的资料,了解到了DOMContentLoaded这个事件,在DOM加载完毕之后就发生了。非常好用,不过只有FF和opera9以上的版本才支持。调用的方法非常简单:

  if (document.addEventListener) {   document.addEventListener("DOMContentLoaded", init, false); } 

当然仅少数浏览器支持(特别是IE不支持)显然不能让他发挥最大的作用。

不久前读John Resig的书,他用了一个不错的判断DOM加载的办法:


function domReady( f ) {
    // If the DOM is already loaded, execute the function right away
    if ( domReady.done ) return f();

    // If we’ve already added a function
    if ( domReady.timer ) {
        // Add it to the list of functions to execute
        domReady.ready.push( f  );
    } else {
        // Attach an event for when the page finishes  loading,
        // just in case it finishes first. Uses addEvent.
        addEvent( window, “load”, isDOMReady );

        // Initialize the array of functions to execute
        domReady.ready = [ f ];

        //  Check to see if the DOM is ready as quickly as possible
        domReady.timer = setInterval( isDOMReady, 13 );
    }
}

// Checks to see if the DOM is ready for navigation
function isDOMReady() {
    // If we already figured out that the page is ready, ignore
    if ( domReady.done ) return false;

    // Check to see if a number of functions and elements are
    // able to be accessed
    if ( document && document.getElementsByTagName && 
          document.getElementById && document.body ) {

        // If they’re ready, we can stop checking
        clearInterval( domReady.timer );
        domReady.timer = null;

        // Execute all the functions that were waiting
        for ( var i = 0; i < domReady.ready.length; i++ )
            domReady.ready[i]();

        // Remember that we’re now done
        domReady.ready = null;
        domReady.done = true;
    }
}



简单地讲就是用document&& document.getElementsByTagName &&document.getElementById&& document.body 去判断Dom树是否加载完毕。

今天又在网上搜了下,发现这个办法不错:
http://dean.edwards.name/weblog/2005/09/busted/
http://dean.edwards.name/weblog/2006/06/again/

对于IE利用条件注释给它动态一个有defer和src属性的脚本。

 
<script defer src="ie_onload.js" type="text/javascript"></script>

什么是defer属性,比较标准的说法是"如果是编写脚本的时候加入defer属性,那么浏览器在下载脚本的时候就不必立即对其进行处理,而是继续对页面进行下载和解析,这样会提高下载的性能。"而利用条件注释又确保了这只在ie浏览器下执行而不会产生不一要的js代码。而对于Safari浏览器,则调用了一个setInterval去判断document的状态。具体可见刚才上面提到的两篇文章。完整的代码见这个[URL=http://dean.edwards.name/my/busted.html ]例子[/URL]。

得到这我又对这段代码进行了必要的改进,原来代码用document.addEventListener做判断,但实施上opera支持addEventListener这个方法,但却只有9及以上的版本支持DOMContentLoaded。另外,对于其他没有涉及的浏览器,它马上进行的是window.onload的操作,我把这个过程有包上了John Resig的判断方法,最后以防万一再调用window.onload进行检查。另外原先的函数只能绑定一个处理函数,我想了想将多个函数名以数组的形式传进来,这个可以按顺序在Dom加载完毕后执行多个函数。整段代码集成在FDev中,源码如下:


domReady: function(fn){
		var isReady = false;
		/* 对 Mozilla/Opera9 (支持DOMContentLoaded)的操作*/
		if (document.addEventListener && !(FUB.isOpera && FUB.version.substring(0,1)<9)){
			document.addEventListener("DOMContentLoaded", init, false);
		}
		/* 对IE的操作,hack方法,添加一个脚本*/
		/*@cc_on @*/
		/*@if (@_win32) 
		document.write("<script id=__ie_onload defer src=javascript:void(0)><\/script>");
		var script = document.getElementById("__ie_onload");
		script.onreadystatechange = function() {
			if (this.readyState == "complete") {
				init(); 	// call the onload handler
			}
		};
		/*@end @*/
		//对Safari的操作
		if (FUB.isSafari) { // sniff  
			var _timer = setInterval(function() {
				if (/loaded|complete/.test(document.readyState)) {
					init(); // call the onload handler
				}
			}, 20);
		}
		//对于其他的浏览器
		var _timer = setInterval(function() {
			if( document && document.getElementsByTagName && document.getElementById && document.body ){
				init();	
			}
		},20);
		//为保险再将window.onload时调用一次,防止意外
		window.onload = function(){
			init()
		};
		function init(){
			//如果本方法已经被调用过则退出
			if (arguments.callee.done){
				return;
			}
			// 为本函数添加标记以便不会被调用两次
			arguments.callee.done = true;
			//终止计时
			if (_timer) {
				clearInterval(_timer);
				_timer = null;
			}
			//依次调用正在等待的所有函数
			for(var i=0;i<fn.length;i++){
				fn[i]();
			}
		}
	}
0分/0个投票
  • quote 1.loveguo
  • 直接使用John Resig的方案即可,为啥还要综合两者呢?有什么优点吗?
  • 2008-5-28 18:10:52 回复该留言

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

Powered By Z-Blog. Templete from Google黑板报.

浙ICP备08015283号 Copyright 2007 space007.com. Some Rights Reserved.