DOM Ready 概述

熟悉jQuery的人, 都知道DomReady事件. window.onload事件是在页面所有的资源都加载完毕后触发的. 如果页面上有大图片等资源响应缓慢, 会导致window.onload事件迟迟无法触发.所以出现了DOM Ready事件. 此事件在DOM文档结构准备完毕后触发, 即在资源加载前触发. 另外我们需要在DOM准备完毕后, 再修改DOM结构, 比如添加DOM元素等. 否则有可能出现“Internet Explorer无法打开站点”的问题. 要模拟此错误, 可以在页面上添加下面的代码, 并用IE6打开:

<div>
    <script type="text/javascript">
        var div = document.createElement('div');
        div.innerHTML = "test";
        document.body.appendChild(div);
    </script>
</div>

有关DOM Ready事件的实现,包括jQuery中的DomReady实现, 在国内和国外网站上已经早有人分享了经验, 并提出了许多方法.

为了避免人云亦云, 抱着怀疑的态度, 我去研究了这些DOM Ready方法. 只会使用Google搜索或者jQuery等类库, 不会帮助前端开发人员进步.所以弄懂其中的原理才是关键.

对于FF, Chrome, Safari, IE9等浏览器:

DOMContentLoaded 事件在许多Webkit浏览器以及IE9上都可以使用, 此事件会在DOM文档准备好以后触发, 包含在HTML5标准中. 对于支持此事件的浏览器, 直接使用DOMContentLoaded事件是最简单最好的选择.

对于IE6,7,8:

不幸的是, IE6,7,8都不支持DOMContentLoaded事件.所以目前所有的hack方法都是为了让IE6,7,8支持DOM Ready事件.

鉴于下面的统计结果(2011年5月统计的数据), 我们必须支持IE6,7,8, 一个都不能少!

中国某音乐站:

CNZZ:

另外鉴于"360浏览器"的占有率, 还要支持"360+IE6"这种无敌组合.

DOM Ready 实现方法

首先总结一下目前IE下的DOM Ready方法:

(1)setTimeout : 在setTimeout中触发的函数, 一定会在DOM准备完毕后触发.

示例代码:

   1: //setTimeout Dom Ready
   2: var setTimeoutReady = function(){
   3:     document.getElementById("divMsg").innerHTML += "<br/> setTimeout , readyState:" + document.readyState;
   4: };
   5: var setTimeoutBindReady = function(){
   6:     /in/.test(document.readyState)?setTimeout(arguments.callee, 1):setTimeoutReady();
   7: };
   8: setTimeoutBindReady();

(2)readyState: 判断readyState的状态是否为Complete, interactive等触发

示例代码:

   1: //onreadystatechange event
   2: document.onreadystatechange = function(e){
   3:     document.getElementById("divMsg").innerHTML += "<br/> onreadystatechange, readyState:" + document.readyState;
   4:  
   5: };

(3)外部script: 通过设置了script块的defer属性实现.

参见: http://dean.edwards.name/weblog/2005/09/busted/

示例代码:

   1: <script type="text/javascript" src="ext-1.js" defer></script>

(4)内部script: 外部script的改进版本. 外部script需要页面引用额外的js文件. 内部script方法可以避免此问题.

参见: http://dean.edwards.name/weblog/2006/06/again/

示例代码:

   1: //script defer    Dom Ready    
   2: document.write("<script id=__ie_onload defer src=javascript:void(0)><\/script>");
   3: var script = document.getElementById("__ie_onload");
   4: script.onreadystatechange = function() {
   5:     if (this.readyState == "complete") {                
   6:         document.getElementById("divMsg").innerHTML += "<br/>internal script defer, readyState:" + document.readyState;
   7:     }
   8: };

(5)doScroll : 微软的文档只出doScroll必须在DOM主文档准备完毕时才可以正常触发. 所以通过doScroll判断DOM是否准备完毕.

参见: http://javascript.nwbox.com/IEContentLoaded/

示例代码:

    //doScroll
    var doScrollMoniterId = null;
    var doScrollMoniter = function(){
        try{
            document.documentElement.doScroll("left");
            document.getElementById("divMsg").innerHTML += "<br/>doScroll, readyState:" + document.readyState;
            if(doScrollMoniterId){
                clearInterval(doScrollMoniterId);
            }
        }
        catch(ex){
        }
    }
    doScrollMoniterId = setInterval(doScrollMoniter, 1);

DOM Ready 调研结论

测试结果见下面. 在这里先给出结论.

  • setTimeout设置的函数, 会在readyState为complete时触发, 但是触发时间点是在图片资源加载完毕后.
  • readyState为interactive时, DOM结构并没有稳定, 此时依然会有脚本修改DOM元素.
  • readyState为complete时, 图片已经加载完毕, 实验中对图片加载设置了延时.所以complete虽然在window.onload前执行, 但是还是太晚.
  • 外部script:如果将此script放在页面上方, 则无法稳定触发. 并且触发时DOM结构依然可能发生变化.
  • 内部script:与外部script同样的问题, 触发的时间过早.
  • doScroll: doScroll通过时readyState可能为interactive, 也可能为complete. 但是一定会在DOM结构稳定后, 图片加载完毕前执行.

所以可以看出, 目前的setTimeout方法, 外部script和内部script方法, 都是存在错误的.应该说这些方法不能安全可靠的实现DomReady事件.

而单纯使用readyState属性是无法判断出Dom Ready事件的. interactive状态过早(DOM没有稳定), complete状态过晚(图片加载完毕).

jQuery实现中使用的doScroll方法是目前唯一可用的方法.

在本文的最后, 提供了使用本原理实现的ready函数. 其实和jQuery中的Dom Ready原理几乎一样. 但是其中加入了延时, 可以指定win对象(即支持iframe)等功能.

360+IE6:

 
add DOM onload, readyState:interactive
internal script defer, readyState:interactive
[延时2秒加载js脚本]
add DOM in delay script, readyState:interactive
jQuery ready, readyState:interactive
doScroll, readyState:interactive
[延时8秒加载图片]
onreadystatechange, readyState:complete
window.onload, readyState:complete
setTimeout , readyState:complete

添加了defer的的外部js都没有执行.

对360这种没有技术, 没有任何优点, 单纯的在IE上面加个外壳, 添加自己的产品和广告的浏览器, 应该予以鄙视! 虽然凭借"安全卫士推荐用浏览器"占有了很多的国内市场, 从运维和产品层面看是成功的, 但是却是不道德的. 所谓的"安全"只是一个幌子而已! 真正了解互联网的用户不应该使用360浏览器. 360安全卫士是一款不错的产品, 最初的定位很好. 只是目前越做越大, 手伸的越来越深. 挟用户威胁厂商的盈利模式.不许别人出广告, 只能自己产品出广告.当一款好的产品在一个没有道德底线的人手里时, 用户变得很难取舍.

IE6:

 
add DOM onload, readyState:interactive
[external script defer(1), readyState:interactive--经常无此项]
internal script defer, readyState:interactive
[延时2秒加载js脚本]
add DOM in delay script, readyState:interactive
external script defer (2), readyState:interactive
jQuery ready, readyState:interactive
doScroll, readyState:interactive
[延时8秒加载图片]
onreadystatechange, readyState:complete
window.onload, readyState:complete
setTimeout , readyState:complete

第一个添加了defer的外部js, 时有时无. 大部分时间没有.

IE7:

 
add DOM onload, readyState:interactive
[external script defer(1), readyState:interactive--经常无此项]
internal script defer, readyState:interactive
add DOM in delay script, readyState:interactive
external script defer (2), readyState:interactive
onreadystatechange, readyState:complete
jQuery ready, readyState:complete
window.onload, readyState:complete
setTimeout , readyState:complete

第一个添加了defer的外部js, 时有时无. 大部分时间没有.

IE8:

 
add DOM onload, readyState:interactive
external script defer (1), readyState:interactive
internal script defer, readyState:interactive
external script defer (2), readyState:interactive
add DOM in delay script, readyState:interactive
onreadystatechange, readyState:complete
jQuery ready, readyState:complete
window.onload, readyState:complete
setTimeout , readyState:complete

IE9:

 
add DOM onload, readyState:interactive
external script defer (1), readyState:interactive
internal script defer, readyState:interactive
external script defer (2), readyState:interactive
add DOM in delay script, readyState:interactive
jQuery ready, readyState:interactive
onreadystatechange, readyState:complete
window.onload, readyState:complete
setTimeout , readyState:complete

DOM Ready实现代码

下面是实现DOM Ready事件的函数代码, 与jQuery的相比, 除了"__proxy函数"(在后面会讲解), 其他的依赖函数都在ready的定义中, 易于理解和维护. 并且自己实现更加具有灵活性, 加入了时间延时已经传递window对象的能力:

        /**
        Dom Ready Event
        */
        ready : function( callback , delay, win){
            win = win || this.win || window;
            var doc = win.document;
            delay = delay || 0;
            this.domReadyMonitorRunTimes = 0;
            
            //将时间函数放入数组, 在DomReady时一起执行.
            this.readyFuncArray = this.readyFuncArray || [];
            this.readyFuncArray.push({func:callback, delay:delay, done:false});
            
            //domReadyMonitor为监控进程的事件处理函数
            var domReadyMonitor = (function(){
                var isReady = false;
                this.domReadyMonitorRunTimes++;
                
                //对于非iframe嵌套的ie6,7,8浏览器, 使用doScroll判断Dom Ready.
                if(this.browser.ie && this.browser.ie<9 && !win.frameElement){
                    try {
                        doc.documentElement.doScroll("left");
                        isReady = true;
                    } 
                    catch(e) {
                    }
                }
                //非ie浏览器
                //如果window.onload和DOMContentLoaded事件都绑定失败, 则使用定时器函数判断readyState.                
                else if(doc.readyState==="complete" || this.domContentLoaded ){
                        isReady = true;
                }
                //对于某些特殊页面, 如果readyState永远不能为complete, 设置了一个最大运行时间5分钟. 超过了最大运行时间则销毁定时器.
                //定时器销毁不影响window.onload和DOMContentLoaded事件的触发.
                else{
                    if(this.domReadyMonitorRunTimes > 300000){
                        if(this.domReadyMonitorId){
                            win.clearInterval(this.domReadyMonitorId);
                            this.domReadyMonitorId = null;
                        }
                        return;
                    }
                }        
                
                   
                //执行ready集合中的所有函数
                if(isReady){
                    try{
                        if(this.readyFuncArray && this.readyFuncArray.length){
                            for(var i=0, count=this.readyFuncArray.length; i<count; i++){
                                var item = this.readyFuncArray[i];
                                if(!item || !item.func || item.done){
                                    continue;
                                }                                    
                                if(!item.delay){    
                                    item.done = true;
                                    item.func();
                                }
                                else{
                                    item.done = true;
                                    win.setTimeout(item.func, item.delay);
                                }
                            }
                        }
                    }
                    catch(ex){
                        throw ex;
                    }
                    finally{
                        if(this.domReadyMonitorId){
                            win.clearInterval(this.domReadyMonitorId);
                            this.domReadyMonitorId = null;
                        }
                    }
               }
            }).__proxy(this);    
            
            /**
            domContentLoadedHandler直接执行所有ready函数.
            没使用传参的形式是因为ff中的定时器函数会传递一个时间参数.
            */
            var domContentLoadedHandler = (function(){
                this.domContentLoaded = true;
                domReadyMonitor();
            }).__proxy(this);
            
            //启动DomReady监控进程
            if(!this.domReadyMonitorStarted){
                this.domReadyMonitorStarted = true;    
                this.domReadyMonitorId = win.setInterval( domReadyMonitor, 50);
                // Mozilla, Opera and webkit nightlies currently support this event
                if ( doc.addEventListener ) {
                    // Use the handy event callback
                    doc.addEventListener( "DOMContentLoaded", domContentLoadedHandler, false );
                    // A fallback to window.onload, that will always work
                    win.addEventListener( "load", domContentLoadedHandler, false );                
                }
                else if(doc.attachEvent){
                    // A fallback to window.onload, that will always work
                    win.attachEvent( "onload", domContentLoadedHandler, false );    
                }
            }                    
        }

上面的ready函数, 使用了一个Function对象的__proxy方法. 这是因为ready函数定义在一个使用JSON格式创建的对象中:

var MyClass = {
    ready : function(){}
}

“__proxy”函数用于修改实现函数的this上下文, 与jQuery中的proxy函数类似, 但是为了使用更加优雅的语法, 所以注入到了Function原型中. 如果不需要修改this可以自行ready函数中对"___proxy"函数的依赖.

"__proxy"函数代码如下:

            //扩展function函数原型。为所有的function对象添加proxy函数,用于修改函数的上下文。
            Function.prototype.__proxy = function(context){
                var method = this;
                return function () {
                    return method.apply(context || {}, arguments);
                };
            }; 

使用举例:

   1:  
   2: //正常使用
   3: this.U.ready( this.windowOnLoadHandler );
   4: //延时2秒加载
   5: this.U.ready(this.windowOnLoadDelayHandler, 2000);

转载:张子秋 http://www.cnblogs.com/zhangziqiu/ 

DOM Ready 详解的更多相关文章

  1. dom对象详解--document对象(二)

       dom对象详解--style对象 style对象 style对象和document对象下的集合对象styleSheets有关系,styleSheets是文档中所有style对象的集合,这里讲解的 ...

  2. dom对象详解--document对象(一)

     document对象 Document对象代表整个html文档,可用来访问页面中的所有元素,是最复杂的一个dom对象,可以说是学习好dom编程的关键所在. Document对象是window对象的一 ...

  3. DOM API详解

    来源于:http://zxc0328.github.io/2016/01/23/learning-dom-part1/ https://zxc0328.github.io/2016/01/26/lea ...

  4. DOM——事件详解

    事件 事件:触发-响应机制 事件三要素 事件源:触发(被)事件的元素 事件名称: click 点击事件 事件处理程序:事件触发后要执行的代码(函数形式) 事件的基本使用  var box = docu ...

  5. dom jaxp详解

    转自 http://blog.csdn.net/java958199586/article/details/7277904 一.XML解析技术概述 1.XML解析方式分为两种:dom和sax (1)d ...

  6. jquery源码解析:expando,holdReady,ready详解

    jQuery的工具方法,其实就是静态方法,源码里面就是通过extend方法,把这些工具方法添加给jQuery构造函数的. jQuery.extend({       //当只有一个对象时,就把这个对象 ...

  7. jQuery的DOM操作详解

    DOM(Document Object Model-文档对象模型):一种与浏览器, 平台, 语言无关的规则, 使用该接口可以轻松地访问页面中所有的标准组件DOM操作的分类 核心-DOM: DOM Co ...

  8. Jqurey DOM 操作详解

    一.获取 1.获取内容----.text()  .html()   .value() text() - 设置或返回所选元素的文本内容                         格式:$(选择器) ...

  9. dom对象详解--document对象(三)

     form对象 form对象代表一个HTML表单,在HTML文档中<form>每出现一次,form对象就会被创建.从dom对象层次图看,document.forms对象是当前文档所有for ...

随机推荐

  1. tomcat热部署,更改java类不用重新加载context

    修改类后,tomcat热部署会重新加载整个项目的context,影响开发效率.网上查的大多数是将server的modules标签中Auto Reload项改为Disabled,但是没有效果. 使用以下 ...

  2. Ubuntu 14.10安装SecureCRT 7.3

    Ubuntu 14.10下安装SecureCRT 7.3 1.软件准备 Ubuntu14.10 x64 SecureCRT7.3的版本:scrt-7.3.0-657.ubuntu13-64.x86_6 ...

  3. 百度之星资格赛——Disk Schedule(双调旅行商问题)

    Disk Schedule Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) To ...

  4. systemtap 列出所有linux 内核模块与相关函数2

    [root@localhost src]# uname -aLinux localhost.localdomain 2.6.32 #1 SMP Sun Sep 20 18:58:21 PDT 2015 ...

  5. 安装64位ubuntu 14.04-搭建android开发环境

                              end

  6. Top 10 Mistakes Java Developers Make--reference

    This list summarizes the top 10 mistakes that Java developers frequently make. #1. Convert Array to ...

  7. TCPDUMP Command Examples

    tcpdump command is also called as packet analyzer. tcpdump command will work on most flavors of unix ...

  8. ios开发时候,出现A valid provisioning profile for this executable was not found 错误

    今天一大早起来,做ios的开发,发现了一下错误:A valid provisioning profile for this executable was not found 错误的产生是在开发模式下产 ...

  9. SignalR: The new old thing

    As you can see, this is my first blog posted in cnblog. If you find any mistake, don’t hesitate to t ...

  10. jasper2

    package jasper; import java.io.File;import java.io.FileOutputStream;import java.io.OutputStream;impo ...