原文地址: 跨浏览器的DOMContentLoaded事件
DOMContentLoaded事件是在DOM已经准备好,而其他资源可能还未下载完毕时触发的。我们可以藉此尽快的给各种交互用的dom绑定交互事件。无奈IE678不支持此事件,需要模拟。能Google到许多相关资源,比如主流框架中DOMContentLoaded事件的实现。
目前,在IE中模拟DOMContentLoaded事件有3种方法。
document.attachEvent("onreadystatechange", function(){ if ( document.readyState === "complete" ) { document.detachEvent( "onreadystatechange", arguments.callee ); //这里触发DOMContentLoaded事件 } });
//IE中 script元素的defer属性指示浏览器在dom ready之后再加载该script document.write("<"+"script id=__onDOMContentLoaded defer src=//:><\/script>"); document.getElementById("__onDOMContentLoaded").onreadystatechange = function() { if (this.readyState == "complete") { this.onreadystatechange = null; //这里触发DOMContentLoaded事件 } };
(function(){ try{ //doScroll方法只有在dom ready之后可以调用,否则会抛异常 document.documentElement.doScroll('left'); }catch(e){ window.setTimeout( arguments.callee, 0 ); return; } //这里触发DOMContentLoaded事件 })();
在3种模拟DOMContentLoaded的方法中,检测document.readyState状态的方法和检测defer的script的方法会因为页面中包含iframe而无法及时触发(要等到iframe加载完毕readyState的值才会变为complete),而调用doScroll的方法可以避免此问题。但是调用doScroll方法在脚本本身运行于iframe中时会在iframe的dom准备好之前就可以使用,我想可能是因为iframe的包含文档已经处于dom ready状态,所以导致doScroll方法可用了。
看了下jQuery-1.4.3和1.6.2的代码,选择了检测document的readyState和调用doScroll方法的组合来模拟DOMContentLoaded事件,基于上述的原因,该模拟可在页面包含iframe情况下检测doScroll方法的调用来模拟DOMContentLoaded,在包含于iframe中时检测document的readyState来模拟DOMContentLoaded。通常很少有在iframe中还有iframe的情况出现吧。jQuery源码中跨浏览器的DOMContentLoaded事件的代码可以简化为以下代码:
(function( window ){ var readyBound = false, readyList = []; var LIZ = { isReady : false, /** * 注册DOM ready时的处理函数 */ onReady: function ( fn ){ LIZ.bindReady(); // 如果此时DOM已经准备好 就直接调用注册的fn if ( LIZ.isReady ) { fn.call( document ); // 否则就把fn推入readyList } else if ( readyList ) { readyList.push( fn ); } }, /** * 触发DOM ready的函数 */ ready: function (){ var fn, i = 0, ready; //如果DOMContentLoaded还没有触发过 if( !LIZ.isReady ){ LIZ.isReady = true; if( readyList ){ //为了减少变量查询带来的性能损耗,将readyList赋值给本地变量 ready = readyList; //释放readyList引用 readyList = null; while( (fn = ready[ i++ ]) ){ fn.call( document ); } } } }, /** * 绑定DOM ready事件监听的函数 */ bindReady: function (){ if ( readyBound ) { return; } readyBound = true; //如果在绑定ready的时候DOM已经准备好了,就异步调用ready函数 if ( document.readyState === "complete" ) { return setTimeout( LIZ.ready, 0 ); } if( document.addEventListener ){ document.addEventListener( "DOMContentLoaded", function(){ document.removeEventListener( "DOMContentLoaded", arguments.callee, false ); LIZ.ready(); }, false ); window.addEventListener( "load", LIZ.ready, false ); } else if( document.attachEvent ){ //如果是IE,先监听document的onreadystatechange事件 //当document.readyState为complete时调用ready函数 //但是当文档中还包含iframe时,readyState要等到iframe加载完毕才会改变状态为complete //所以用下面的doScroll方法来检测DOM是否加载完毕 document.attachEvent("onreadystatechange", function(){ if ( document.readyState === "complete" ) { document.detachEvent( "onreadystatechange", arguments.callee ); LIZ.ready(); } }); window.attachEvent( "onload", LIZ.ready ); var isToplevel = false; try { //判断文档是否处于最顶层 isToplevel = window.frameElement == null; } catch(e) {} //如果文档处于iframe中,调用doScroll方法成功时并不代表DOM加载完毕 //所以用isToplevel标识是否使用doScroll来触发ready函数 if ( document.documentElement.doScroll && isToplevel ) { (function(){ try{ document.documentElement.doScroll('left'); }catch(e){ window.setTimeout( arguments.callee, 0 ); return; } LIZ.ready(); })(); } } } }; window.LIZ = LIZ; })(window);
jQuery源码中对DOMContentLoaded事件的模拟还考虑了js异步加载的解决方案,比如异步加载jQuery的一些插件时,也要保证其代码中绑定的ready事件可以执行。为此jQuery使用一个readyWait属性来记录异步加载代码的数量,可以通过对该属性的操作来延迟DOM ready事件的触发。jQuery1.4版本中就有该属性及相关代码的存在,但是直到jQuery1.6才出现操作该属性的公开的jQuery.holdReady方法。
关于异步加载还要研究研究,还有模块化开发,最近正在看seajs的源码,看完了再写篇读后感-。-