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. iOS开发中打开本地应用、打开appStore应用、给app评分功能实现

    app开发中,通常会有邀请用户给app打分的功能.而在iOS中,正式应用都是通过appStore 下载的,因此给app 打分也只能在 appStore中.因此,需要从应用跳转到appStore.方法是 ...

  2. irefox 34的"Manage Search Engine"去哪了

    博客搬到了fresky.github.io - Dawei XU,请各位看官挪步.最新的一篇是:irefox 34的"Manage Search Engine"去哪了.

  3. iOS Xcode8的适配

    1.打开应用后打印了很多的log信息 解决方法: Xcode8里边 Edit Scheme... -> Run -> Arguments, 在Environment Variables里边 ...

  4. 【系列】Matei Zaharia(Spark系统作者)博士论文-0 摘要

    随着处理器提升速度下降和数据量的不断增长,非常多公司和组织(既有互联网公司也有传统的企业另一些研究机构)都要求他们的应用可以Scale out到更大的分布式系统上(比方整个数据中心). 这些应用又分为 ...

  5. hud 1241 Oil Deposits

    Oil Deposits Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Tot ...

  6. 职责链模式vs状态模式区别

    状态模式在具体状态里设置了下一状态. 而职责链模式是在客户端代码里设置了下一状态的处理对象. 如果状态模式里的任何一环缺失,将导致事情无法进行下去.职责链模式的链式在客户端连接的,也就是说,如果我们请 ...

  7. Emmet最全提示说明

    前一篇文章简单了说明了Emmet的使用:Emmet相关使用 今天我就将关于subline中Emmet插件的snippets.json文件进行具体说明, 在snippets.json文档中,我们能够看到 ...

  8. My97DatePicker日期控件使用方法

    My97DatePicker是一款网页版非常简单而且好用的日期控件,其实几年前就使用过了,这次再次用到,总结下: 首先去官网下载地址:http://www.my97.net/dp/down.asp 在 ...

  9. react 编写组件 五

    看以下示例了解如何定义一个组件 // 定义一个组件LikeButton var LikeButton = React.createClass({ // 给state定义初始值 getInitialSt ...

  10. 【转】浅谈Java中的hashcode方法(这个demo可以多看看)

    浅谈Java中的hashcode方法 哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率.在Java的Object类中有一个方法: public native i ...