相信但凡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]();
}
}
}
