It turns out that the more commonly used one is the onload event of window, and the actual effect of this event is: it is triggered after the page parsing/DOM tree establishment is completed, and the download of all resources such as pictures, scripts, style sheets and even iframes is completed. This is a bit "too late" for many practical applications and affects the user experience. In order to solve this problem, a DOMContentLoaded method was added to ff. Compared with onload, this method is triggered earlier. It is triggered after the DOM content of the page is loaded without waiting for other resources to be loaded. The Webkit engine also introduced this event starting from version 525 (Webkit nightly 1/2008:525), and Opera also includes this method. So far, mainstream IE still has no intention to add it. Although it is not available under IE, there is always a solution. The following is a comparison of the compatibility version implementation plans of several major mainstream frameworks for this event. The frameworks involved include:
Prototype
jQeury
moontools
dojo
yui
ext
1. Prototype
Implementation code
(function() {
/* Support for the DOMContentLoaded event is based on work by Dan Webb,
Matthias Miller, Dean Edwards and John Resig. */
var timer;
function fireContentLoadedEvent() {
if (document.loaded) return;
if (timer) window.clearInterval(timer);
document.fire("dom :loaded");
document.loaded = true;
}
if (document.addEventListener) {
if (Prototype.Browser.WebKit) {
timer = window.setInterval(function () {
if (/loaded|complete/.test(document.readyState))
fireContentLoadedEvent();
}, 0);
Event.observe(window, "load", fireContentLoadedEvent );
} else {
document.addEventListener("DOMContentLoaded",
fireContentLoadedEvent, false);
}
} else {
document.write("<" "script id=__onDOMContentLoaded defer src=//:>");
$("__onDOMContentLoaded").onreadystatechange = function() {
if (this.readyState == "complete") {
this.onreadystatechange = null;
fireContentLoadedEvent();
}
};
}
})();
The implementation idea is as follows:
If it is webkit, poll the readyState attribute of the document. If the value of this attribute is loaded or complete, the DOMContentLoaded event will be triggered. To be on the safe side, register the event on window.onload.
If it is FF, register the DOMContentLoaded event directly.
If it is IE, use document.write to add a script element to the page, and set the defer attribute. Finally, the completion of loading of the script is regarded as a DOMContentLoaded event to trigger.
There are two main problems with this implementation: First, the method of writing script through document.write and setting defer will wait until the content in the iframe is loaded before triggering when the page contains an iframe. This is different from onload. There is not much difference; secondly, Webkit introduced the DOMContentLoaded method in versions above 525, so there is no need to implement polling in these versions and can be optimized.
2. jQuery
function bindReady(){
if ( readyBound ) return;
readyBound = true;
// Mozilla, Opera and webkit nightlies currently support this event
if ( document.addEventListener ) {
/ / Use the handy event callback
document.addEventListener( "DOMContentLoaded", function(){
document.removeEventListener( "DOMContentLoaded", arguments.callee, false );
jQuery.ready();
}, false );
// If IE event model is used
} else if ( document.attachEvent ) {
// ensure firing before onload,
// maybe late but safe also for iframes
document.attachEvent("onreadystatechange", function(){
if ( document.readyState === "complete" ) {
document.detachEvent( "onreadystatechange", arguments.callee );
jQuery.ready();
}
});
// If IE and not an iframe
// continually check to see if the document is ready
if ( document.documentElement .doScroll && typeof window.frameElement === "undefined" ) (function(){
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( error ) {
setTimeout( arguments.callee, 0 );
return;
}
// and execute any waiting functions
jQuery.ready();
})();
}
// A fallback to window.onload, that will always work
jQuery.event.add( window, "load", jQuery.ready );
}
The implementation idea is as follows:
Treat Webkit and Firefox equally, and both directly register the DOMContentLoaded event. However, since Webkit was only introduced in version 525 or above, there are potential compatibility risks.
For IE, first register the onreadystatechange event of the document. After testing, this method is equivalent to window.onload. It will still wait until all resources are downloaded before triggering.
After that, if it is determined that it is IE and the page is not in an iframe, the doScroll method of documentElement will be continuously called through setTiemout until the call is successful and DOMContentLoaded will be triggered
jQuery uses a new solution for IE. Method, which originates from http://javascript.nwbox.com/IEContentLoaded/. Its principle is that under IE, some methods of the DOM can only be called after the DOM parsing is completed. doScroll is such a method. In turn, when doScroll can be called, it is when the DOM parsing is completed, which is the same as in the prototype. Compared with document.write, this solution can solve the problem of failure when the page has an iframe. In addition, jQuery seems to be worried that this method will fail when the page is in an iframe, so it makes a judgment in the implementation code. If it is in an iframe, it is implemented through the onreadystatechange of the document, otherwise it is implemented through doScroll. However, after testing, doScroll is still effective even in an iframe.
3. Moontools
(function(){
var domready = function(){
if (Browser.loaded) return;
Browser.loaded = true;
window.fireEvent('domready');
document.fireEvent('domready');
};
if (Browser.Engine.trident){
var temp = document.createElement('div');
(function(){
($try(function(){
temp.doScroll('left');
return $(temp).inject(document.body).set('html', 'temp').dispose ();
})) ? domready() : arguments.callee.delay(50);
})();
} else if (Browser.Engine.webkit && Browser.Engine.version < ; 525){
(function(){
(['loaded', 'complete'].contains(document.readyState)) ? domready() : arguments.callee.delay(50);
})();
} else {
window.addEvent('load', domready);
document.addEvent('DOMContentLoaded', domready);
}
})() ;
The implementation idea is as follows: If it is IE, use the doScroll method to implement it.
If it is a version of Webkit less than 525, it is implemented by polling document.readyState.
Others (FF/Webkit high version/Opera) directly register the DOMContentLoaded event
Moontools' implementation solution prototype and jQeury are integrated, and the version judgment of webkit makes the solution more robust. In terms of the implementation of doScroll, compared with jQuery, a new div element is created here and destroyed after use. However, jQuery directly uses doScroll of documentElement to detect, which is simpler and more efficient.
4. Dojo
// START DOMContentLoaded
// Mozilla and Opera 9 expose the event we could use
if(document.addEventListener){
// NOTE:
// due to a threading issue in Firefox 2.0, we can't enable
// DOMContentLoaded on that platform. For more information, see:
// http://trac.dojotoolkit.org/ticket/1704
if(dojo.isOpera || dojo.isFF >= 3 || (dojo.isMoz && dojo.config.enableMozDomContentLoaded === true)){
document.addEventListener("DOMContentLoaded", dojo._loadInit, null);
}
// mainly for Opera 8.5, won't be fired if DOMContentLoaded fired already.
// also used for Mozilla because of trac #1640
window.addEventListener("load", dojo._loadInit, null);
}
if(dojo.isAIR){
window.addEventListener("load", dojo._loadInit, null);
}else if(/(WebKit|khtml)/i.test(navigator.userAgent)){ // sniff
dojo._khtmlTimer = setInterval(function(){
if(/loaded|complete/.test(document.readyState)){
dojo._loadInit(); // call the onload handler
}
}, 10);
}
// END DOMContentLoaded
(function(){
var _w = window;
var _handleNodeEvent = function(/*String*/evtName, /*Function*/fp){
// summary:
// non-destructively adds the specified function to the node's
// evtName handler.
// evtName: should be in the form "onclick" for "onclick" handlers.
// Make sure you pass in the "on" part.
var oldHandler = _w[evtName] || function(){};
_w[evtName] = function(){
fp.apply(_w, arguments);
oldHandler.apply(_w, arguments);
};
};
if(dojo.isIE){
// for Internet Explorer. readyState will not be achieved on init
// call, but dojo doesn't need it however, we'll include it
// because we don't know if there are other functions added that
// might. Note that this has changed because the build process
// strips all comments -- including conditional ones.
if(!dojo.config.afterOnLoad){
document.write(''
''
);
}
try{
document.namespaces.add("v","urn:schemas-microsoft-com:vml");
document.createStyleSheet().addRule("v\:*", "behavior:url(#default#VML)");
}catch(e){}
}
// FIXME: dojo.unloaded requires dojo scope, so using anon function wrapper.
_handleNodeEvent("onbeforeunload", function() {
dojo.unloaded();
});
_handleNodeEvent("onunload", function() {
dojo.windowUnloaded();
});
})();
实现思路如下: 如果是Opera或FF3以上版本则直接注册DOMContentLoaded<事件,为保险起见,同时也注册了window.onload事件。
对于webkit则通过轮询document.readyState来实现。
如果是Air则只注册widnow.onload事件。
如果是IE则通过往页面写带defer属性的script并注册其onreadystatechange事件来实现。
Dojo在IE下的实现方案同样无法解决iframe的问题,而由于在FF2 下会有一个非常奇怪的Bug,因此默认只在FF3以上版本上使用DOMContentLoaded事件,同时又给了一个配置 -dojo.config.enableMozDomContentLoaded,如果在FF下将该配置设置为true则依然会使用 DOMContentLoaded来实现,这一点充分考虑到了灵活性。对于webkit的实现,与prototype一样有优化的空间。
五、YUI
(function() {
/*! DOMReady: based on work by: Dean Edwards/John Resig/Matthias Miller */
// Internet Explorer: use the readyState of a defered script.
// This isolates what appears to be a safe moment to manipulate
// the DOM prior to when the document's readyState suggests
// it is safe to do so.
if (EU.isIE) {
// Process onAvailable/onContentReady items when the
// DOM is ready.
YAHOO.util.Event.onDOMReady(
YAHOO.util.Event._tryPreloadAttach,
YAHOO.util.Event, true);
var n = document.createElement('p');
EU._dri = setInterval(function() {
try {
// throws an error if doc is not ready
n.doScroll('left');
clearInterval(EU._dri);
EU._dri = null;
EU._ready();
n = null;
} catch (ex) {
}
}, EU.POLL_INTERVAL);
// The document's readyState in Safari currently will
// change to loaded/complete before images are loaded.
} else if (EU.webkit && EU.webkit < 525) {
EU._dri = setInterval(function() {
var rs=document.readyState;
if ("loaded" == rs || "complete" == rs) {
clearInterval(EU._dri);
EU._dri = null;
EU._ready();
}
}, EU.POLL_INTERVAL);
// FireFox and Opera: These browsers provide a event for this
// moment. The latest WebKit releases now support this event.
} else {
EU._simpleAdd(document, "DOMContentLoaded", EU._ready);
}
/////////////////////////////////////////////////////////////
EU._simpleAdd(window, "load", EU._load);
EU._simpleAdd(window, "unload", EU._unload);
EU._tryPreloadAttach();
})();
实现思路与Moontools一样
六、EXT
function initDocReady(){
var COMPLETE = "complete";
docReadyEvent = new Ext.util.Event();
if (Ext.isGecko || Ext.isOpera) {
DOC.addEventListener(DOMCONTENTLOADED, fireDocReady, false);
} else if (Ext.isIE){
DOC.write("");
DOC.getElementById(IEDEFERED).onreadystatechange = function(){
if(this.readyState == COMPLETE){
fireDocReady();
}
};
} else if (Ext.isWebKit){
docReadyProcId = setInterval(function(){
if(DOC.readyState == COMPLETE) {
fireDocReady();
}
}, 10);
}
// no matter what, make sure it fires on load
E.on(WINDOW, "load", fireDocReady);
}
实现思路与Dojo的一致,不再赘诉。
总结
总结各大主流框架的做法,写了以下这个版本。主要是尽量的做到优化并考虑到FF2下的Bug,提供一个是否使用DOMContentLoaded的开关配置。
/*
* 注册浏览器的DOMContentLoaded事件
* @param { Function } onready [必填]在DOMContentLoaded事件触发时需要执行的函数
* @param { Object } config [可选]配置项
*/
function onDOMContentLoaded(onready,config){
//浏览器检测相关对象,在此为节省代码未实现,实际使用时需要实现。
//var Browser = {};
//设置是否在FF下使用DOMContentLoaded(在FF2下的特定场景有Bug)
this.conf = {
enableMozDOMReady:true
};
if( config )
for( var p in config)
this.conf[p] = config[p];
var isReady = false;
function doReady(){
if( isReady ) return;
//确保onready只执行一次
isReady = true;
onready();
}
/*IE*/
if( Browser.ie ){
(function(){
if ( isReady ) return;
try {
document.documentElement.doScroll("left");
} catch( error ) {
setTimeout( arguments.callee, 0 );
return;
}
doReady();
})();
window.attachEvent('onload',doReady);
}
/*Webkit*/
else if (Browser.webkit && Browser.version < 525){
(function(){
if( isReady ) return;
if (/loaded|complete/.test(document.readyState))
doReady();
else
setTimeout( arguments.callee, 0 );
})();
window.addEventListener('load',doReady,false);
}
/*FF Opera 高版webkit 其他*/
else{
if( !Browser.ff || Browser.version != 2 || this.conf.enableMozDOMReady)
document.addEventListener( "DOMContentLoaded", function(){
document.removeEventListener( "DOMContentLoaded", arguments.callee, false );
doReady();
}, false );
window.addEventListener('load',doReady,false);
}
}