如何监听页面就绪(早于windowonload)

本文探讨了在DOM树就绪时启动JavaScript的优势及其实现方法,对比window.onload事件,介绍了不同浏览器下(如Firefox、Opera、Internet Explorer、Safari)的兼容性处理策略。

原文地址:http://www.javascriptkit.com/dhtmltutors/domready.shtml

在DOM树就绪时就启动页面执行JavaScript(而不是使用window.onload)

 

如下的例子展示了一个js语法中最常用的检查页面是否加载完毕的事件:

 

window.onload=function(){
    walkmydog();
}

 

这种调用方式几乎是我们监听web文档是否加载完毕使用最频繁的写法。因为通过将页面js入口控制在window.onload之内,你就可以放心的在任何地方引用js文件而不用担心js执行的时候页面还没有准备好。对于这个方法有一点需要注意的就是,这个方法会在整个页面完全加载就绪之后才出发,而不管页面上加载的内容是不是我们真正需要的。因此,也就导致了window.onload被众人诟病的缺点——window.onload需要等待整个页面的字面内容全部加载,这意味着会等待形如图片、iframe框架都加载完毕并呈现后,window.onload方法才执行,很多内容往往不是我们必须等待的。这就是为什么Dean Edwards提供了一种替代方案来侦测DOM是否就绪(即指检测DOM树完成初始化),而不是整个文档和所有对象都就绪。举个典型的例子:向DOM树插入/删除节点,这个操作实际上可以被完成的更早,即DOM树被加载完成而不是整个HTML文档被加载并渲染好(放在window.onload中往往导致了性能损失)。根据这样的指导意见,我或许能提炼一下Dean Edwards对于在DOM树就绪后就尽早执行js的技术。

如下是一个我们将要讨论的技术的一个简单示例

·Firefox和Opera9+中侦听DOM就绪

在Firefox和Mozilla,检查DOM树就绪时执行js是很简单的,我们可以在调用document.addEventListener()的时候监听一个特定事件"DOMContentLoaded":

//FF&Opera
if (document.addEventListener)
    document.addEventListener("DOMContentLoaded", walkmydog, false)

在这种情况下walkmydog()方法将会在文档的DOM树初始化后就被执行,他将会在window.onload方法之前多快被执行取决于页面所包含的内容(包含越多的图片、iframe框架,时间差距就会越大)。

·Internet Explorer侦听DOM就绪

在IE(包括IE7)中,没有一个原生的方法来侦测DOM树就绪。Dean Edwards提供了一种圆滑(绕圈子)的解决方案,这种方案依赖于页面内js标签的“延迟”(defer属性)特性。简而言之这个特性使得IE可以“推迟”加载外部js文件直到DOM树被加载完毕,这是一种在IE浏览器内监听DOM树就绪最优的解决方案:

//IE
if (document.all && !window.opera){ //Crude test for IE
    //Define a "blank" external JavaScript tag
    document.write('<script type="text/javascript" id="contentloadtag" defer="defer" src="javascript:void(0)"><\/script>')
    var contentloadtag=document.getElementById("contentloadtag")
    contentloadtag.onreadystatechange=function(){
        if (this.readyState=="complete")
            walkmydog()
    }
}

正如你所见,这项技术以定义一个带有”defer”属性的空的外部js标签为核心。在IE中,大部分元素支持"onreadystatechange"事件,这个事件会在元素加载过程中的每一个阶段触发,并最终以”complete”结束。因此通过监听一个带有”defer”属性的空的外部js标签的onreadystatechange事件,我们就可以准确的在DOM树就绪的时候执行js方法。

·合并处理,以及一个回退策略

你刚刚见识了两个分散的技术来侦测DOM树就绪。现在让我们把分散的两个方法合并在一起,更重要的,下面的例子为不支持前述两种方法的浏览器实现一个回退机制,这个回退策略就是使用window.onload:

//fall back
var alreadyrunflag=0 //flag to indicate whether target function has already been run

if (document.addEventListener)
    document.addEventListener("DOMContentLoaded", function(){alreadyrunflag=1; walkmydog()}, false)
else if (document.all && !window.opera){
    document.write('<script type="text/javascript" id="contentloadtag" defer="defer" src="javascript:void(0)"><\/script>')
    var contentloadtag=document.getElementById("contentloadtag")
    contentloadtag.onreadystatechange=function(){
        if (this.readyState=="complete"){
            alreadyrunflag=1
            walkmydog()
        }
    }
}

window.onload=function(){
    setTimeout("if (!alreadyrunflag) walkmydog()", 0)
}

注意第5,17,18,19行的代码-这就是我们在不支持侦测DOM就绪方法的浏览器中实现的回退机制,我们使用了window.onload事件来代替。有了这样的处理,现代浏览器会在DOM树就绪后立即执行,老旧浏览器会在window.onload方法触发时执行js代码,所有浏览器都会在页面就绪后执行。为了使这个方案能够正常工作,有两点我想提醒各位:

(1)上述代码定义了一个全局变量"alreadyrunflag",来使执行回退方案的浏览器知道是否需要通过window.onload来执行js方法。如果标记表明"true",就表明js方法在早先时候DOM树就绪的阶段已经被运行过,window.onload方法触发时就不会再重复执行。

(2)针对上述代码,在IE中启动非常简单的页面时,可能会出现window.onload和DOM树就绪(外部js标签readyState变成"complete" )同时触发的情况。在这种特殊情况下,在我们的回退方案执行的时候alreadyrunflag标记也许不会变成true,IE浏览器也就不知道js方法已经被执行过,可能导致绑定的js方法将会执行两编。这个问题可以通过在window.onload中编写一个嵌入式的setTimeout()来解决,从而确保相关代码会被紧跟在当前代码之后被执行。

通过这种方式你就拥有了一个能使js尽早在DOM树加载完毕后就立即执行的方法,而不会在页面尚未准备完毕时就提前执行。使用相同的技巧,下面的例子展示了如何执行两个方法,并且后者会传入一个参数:

//two functions
var alreadyrunflag=0 //flag to indicate whether target function has already been run

if (document.addEventListener)
    document.addEventListener("DOMContentLoaded", function(){alreadyrunflag=1; walkmydog(); feedcat('grapes')}, false)
else if (document.all && !window.opera){
    document.write('<script type="text/javascript" id="contentloadtag" defer="defer" src="javascript:void(0)"><\/script>')
    var contentloadtag=document.getElementById("contentloadtag")
    contentloadtag.onreadystatechange=function(){
        if (this.readyState=="complete"){
            alreadyrunflag=1
            walkmydog()
            feedcat('grapes')
        }
    }
}

window.onload=function(){
    setTimeout("if (!alreadyrunflag){walkmydog(); feedcat('grapes')}", 0)
}

·总结,以及在Safari中侦测DOM就绪

使用上述方法来使js在DOM树就绪时就立即执行而不是等到window.onload触发才执行的优点体现在一些比较复杂的页面上或者iframe标签里。最终你会从这些额外的代码和工作中获得足够的收益。在我总结这份文章之前,房间里还有一头大象一直被我忽视到现在(即指没有提供Safari的兼容方案)。在Safari中实际上也是可以侦测DOM就绪事件的,Dean Edward提到John Resig(jQuery作者)为这种浏览器提供了一种方案:

//Safari
if(/Safari/i.test(navigator.userAgent)){ //Test for Safari
    var _timer=setInterval(function(){
            if(/loaded|complete/.test(document.readyState)){
                clearInterval(_timer)
                walkmydog() // call target function
            }}
        , 10)
}

这是一个DOM树就绪检查器!在Safari中,实际上,你需要做的就是在一个快速的轮询定时器中手工的去探查document.readyState属性是否变成了"loaded" 或"completed"状态(通常前者会早于后者,但是为了保险起见,我们将一起监听两个状态)。这就是DOM树就绪并执行你的js方法的时刻。就我个人而言,我并不热衷于实现这个方法,因为这种实现方式跨越了技术底线:成本高于了收益(译者注:正则表达式以及轮询定时器的使用将会极大的损耗性能)。不过最终我们都有权利选择我们使用的技术方案。

 

熬夜不易,请作者喝杯酒!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值