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(); }
有任何问题,欢迎留言交流。
注意:已解决的问题,会在整理后删除掉。
*******站在巨人的肩膀上