IE模拟addDOMLoadEvent和jQuery的ready实现

由于 window.onload 事件需要在页面所有内容(包括图片等)加载完后,才执行,但往往我们更希望在 DOM 一加载完就执行脚本。其实在现在大部分主流浏览器上(Firefox 3+,Opera 9+,Safari 3+,Chrome 2+)都提供了这一事件方法:addDOMLoadEvent

document.addEventListener("DOMContentLoaded", init,false);

dom渲染完成,doScroll方法从不能执行到可执行,这样我们在IE中,利用 doScroll()方法来模拟 addDOMLoadEvent 事件,现在主流的 JavaScript 框架(JQuery、YUI等)基本都采用的这一解决方案。

/*
 *
 * IEContentLoaded.js
 *
 * Author: Diego Perini (diego.perini at gmail.com) NWBOX S.r.l.
 * Summary: DOMContentLoaded emulation for IE browsers
 * Updated: 05/10/2007
 * License: GPL/CC
 * Version: TBD
 *
 */
// @w    window对象的引用
// @fn   回调函数
function IEContentLoaded(w, fn) {
    var d = w.document,
    done = false,
    // 只执行一次
    init = function() {
        if (!done) {
            done = true;
            fn();
        }
    }; // 循环执行,直到无错误
    (function() {
        try { // 一直抛出错误,直到dom渲染结束
            d.documentElement.doScroll('left');
        } catch(e) {
            setTimeout(arguments.callee, 50);
            return;
        } // 没有错误,执行函数
        init();
    })(); // 双重保险,绑定事件
    d.onreadystatechange = function() {
        if (d.readyState == 'complete') {
            d.onreadystatechange = null;
            init();
        }
    };
}

参考于 http://www.planabc.net/2009/07/30/adddomloadevent/

 

jQuery1.4.4中ready实现

jQuery.fn = jQuery.prototype = {
    // ...
    ready: function( fn ) {
            // 绑定上监听事件
            jQuery.bindReady();

            // 如果dom已经渲染
            if ( jQuery.isReady ) {
                // 立即执行
                fn.call( document, jQuery );

            // 否则,保存到缓冲队列,等上面的监听事件触发时,再全部执行
            } else if ( readyList ) {
                // 将回调增加到队列中
                readyList.push( fn );
            }

            return this;
        },
    // ...
};    
jQuery.extend({
// ...
    // dom是否渲染完成标志
    isReady: false,

    // 一个计数器,用于跟踪在ready事件出发前的等待次数
    // 预留,支持于更高版本jQuery提供的ready等待方法
    readyWait: 1,
    
    // 文档加载完毕句柄,dom渲染完成后执行
    ready: function( wait ) {
        // A third-party is pushing the ready event forwards
        if ( wait === true ) {
            jQuery.readyWait--;
        }

        // Make sure that the DOM is not already loaded
        if ( !jQuery.readyWait || (wait !== true && !jQuery.isReady) ) {
            // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
            // 确保body元素存在,这个操作是防止IE的bug
            if ( !document.body ) {
                return setTimeout( jQuery.ready, 1 );
            }

            // dom渲染完成标志设置为true
            jQuery.isReady = true;

            // If a normal DOM Ready event fired, decrement, and wait if need be
            if ( wait !== true && --jQuery.readyWait > 0 ) {
                return;
            }

            // 绑定的渲染完成后的执行函数
            if ( readyList ) {
                // 全部执行
                var fn,
                    i = 0,
                    ready = readyList;

                // 重置
                readyList = null;

                while ( (fn = ready[ i++ ]) ) {
                    fn.call( document, jQuery );
                }

                // 触发所有ready事件
                if ( jQuery.fn.trigger ) {
                    jQuery( document ).trigger( "ready" ).unbind( "ready" );
                }
            }
        }
    },
    // 初始化readyList事件处理函数队列
    // 兼容不同浏览对绑定事件的区别
    bindReady: function() {
        if ( readyBound ) {
            return;
        }

        readyBound = true;

        // $(document).ready()的嵌套调用时
        // readyState: "uninitalized"、"loading"、"interactive"、"complete" 、"loaded"
        if ( document.readyState === "complete" ) {
            // 让它异步执行,使这个ready能延迟
            return setTimeout( jQuery.ready, 1 );
        }

        // Mozilla, Opera and webkit 
        // 兼容事件,通过检测浏览器的功能特性,而非嗅探浏览器
        if ( document.addEventListener ) {
            // 使用事件回调函数
            document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
            
            // 绑定回调到load,使之能一定执行
            window.addEventListener( "load", jQuery.ready, false );

        // IE
        } else if ( document.attachEvent ) {
            // 确保在load之前触发onreadystatechange,
            // 针对iframe情况,可能有延迟
            document.attachEvent("onreadystatechange", DOMContentLoaded);
            
            // 绑定回调到一定执行能load事件
            window.attachEvent( "onload", jQuery.ready );

            // 如果是IE且非iframe情况下
            // 持续的检查,看看文档是否已准备
            var toplevel = false;

            try {
                toplevel = window.frameElement == null;
            } catch(e) {}

            if ( document.documentElement.doScroll && toplevel ) {
                doScrollCheck();
            }
        }
    },

// ...
});    
if ( document.addEventListener ) {
    DOMContentLoaded = function() {
        document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
        jQuery.ready();
    };

} else if ( document.attachEvent ) {
    DOMContentLoaded = function() {
        // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
        if ( document.readyState === "complete" ) {
            document.detachEvent( "onreadystatechange", DOMContentLoaded );
            jQuery.ready();
        }
    };
}
// IE循环检查dom是否渲染完毕
function doScrollCheck() {
    if ( jQuery.isReady ) {
        return;
    }

    try {
        // If IE is used, use the trick by Diego Perini
        // http://javascript.nwbox.com/IEContentLoaded/
        document.documentElement.doScroll("left");
    } catch(e) {
        setTimeout( doScrollCheck, 1 );
        return;
    }

    // 执行在等待的函数
    jQuery.ready();
}

 将jQuery的ready方法剥离出,提取为函数ready及测试例子

 

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta http-equiv="content-type" content="text/html;charset=utf-8">
    <script type="text/javascript">
        (function(window, undefined) {
            var readyList = [],
                    isReady = 0,
                    readyBound = false,
                    init,
                    bindReady,
                    readyWait = 1;
            init = function(wait) { // A third-party is pushing the ready event forwards
                if (wait === true) {
                    readyWait--;
                } // Make sure that the DOM is not already loaded
                if (!readyWait || (wait !== true && !isReady)) { // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
                    // 确保body元素存在,这个操作是防止IE的bug
                    if (!document.body) {
                        return setTimeout(init, 1);
                    } // dom渲染完成标志设置为true
                    isReady = true; // If a normal DOM Ready event fired, decrement, and wait if need be
                    if (wait !== true && --readyWait > 0) {
                        return;
                    } // 绑定的渲染完成后的执行函数
                    if (readyList) { // 全部执行
                        var fn, i = 0,
                                ready = readyList; // 重置
                        readyList = null;
                        while ((fn = ready[i++])) {
                            fn.call(document);
                        }
                    }
                }
            }; // 初始化readyList事件处理函数队列
            // 兼容不同浏览对绑定事件的区别
            bindReady = function() {
                if (readyBound) {
                    return;
                }
                readyBound = true; // $(document).ready()的嵌套调用时
                // readyState: "uninitalized"、"loading"、"interactive"、"complete" 、"loaded"
                if (document.readyState === "complete") { // 让它异步执行,使这个ready能延迟
                    return setTimeout(init, 1);
                } // Mozilla, Opera and webkit
                // 兼容事件,通过检测浏览器的功能特性,而非嗅探浏览器
                if (document.addEventListener) { // 使用事件回调函数
                    document.addEventListener("DOMContentLoaded",
                            function() {
                                document.removeEventListener("DOMContentLoaded", arguments.callee, false);
                                init();
                            },
                            false); // 绑定回调到load,使之能一定执行
                    window.addEventListener("load", init, false); // IE
                } else if (document.attachEvent) { // 确保在load之前触发onreadystatechange,
                    // 针对iframe情况,可能有延迟
                    document.attachEvent("onreadystatechange",
                            function() { // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
                                if (document.readyState === "complete") {
                                    document.detachEvent("onreadystatechange", arguments.callee);
                                    init();
                                }
                            }); // 绑定回调到一定执行能load事件
                    window.attachEvent("onload", init); // 如果是IE且非iframe情况下
                    // 持续的检查,看看文档是否已准备
                    var toplevel = false;
                    try {
                        toplevel = window.frameElement == null;
                    } catch(e) {}
                    (function() {
                        if (document.documentElement.doScroll && toplevel) {
                            if (isReady) {
                                return;
                            }
                            try { // If IE is used, use the trick by Diego Perini
                                // http://javascript.nwbox.com/IEContentLoaded/
                                document.documentElement.doScroll("left");
                            } catch(e) {
                                setTimeout(arguments.callee, 1);
                                return;
                            } // 执行在等待的函数
                            init();
                        }
                    })();
                }
            };
            window.ready = function(fn) { // 绑定上监听事件
                bindReady(); // 如果dom已经渲染
                if (isReady) { // 立即执行
                    fn.call(document); // 否则,保存到缓冲队列,等上面的监听事件触发时,再全部执行
                } else if (readyList) { // 将回调增加到队列中
                    readyList.push(fn);
                }
            };
        })(window);
        ready(function(){
            var inp = document.getElementById('inp');
            console.log(inp.nodeName);
        });
        ready(function(){
            console.log('second ready');
            ready(function(){
                console.log('in the second ready')
            })
        })
    </script>
</head>
<body>
  <input type="text" id="inp"/>
</body>
</html>
        <script type="text/javascript">
            ready(function(){
                console.log('third ready');
            });
        </script>

 平时测试简单方法

 

// 来源于 http://www.planabc.net/2011/05/26/domready_function/
function domReady(fn) {
    // "uninitalized"、"loading"、"interactive"、"complete" 、"loaded"
    /in/.test(document.readyState) ? window.setTimeout(function() { domReady(fn); }, 10) : fn();
}

 

 

 

 

posted @ 2013-05-22 23:25  小方。  阅读(1168)  评论(5编辑  收藏  举报