JavaScript页面文档初始化 window.onload的替代办法
原文:http://www.qiqiboy.com/2010/08/08/javascript-page-document-initialize-window-onload-alternative.html
另一篇http://www.vgot.net/?A65.htm
很早,大概去年就在mg12的博客看到他一篇很久以前的文章《 JavaScript 初始化装载方法 》,当时也没注意。这两天在做页面一些部分的延迟加载时,又想起了这个,因为使用window.onload,实在是一个很不明智的选择,很可能一个坏掉的图片或什么的就会导致你的JS迟迟装载不了,无法执行。
我试用了一下mg12文章中的方法,发现一个严重的错误,那就是在IE下如果同时绑定多个函数,那么就只会有最后一个执行。仔细分析了一下,原来是因为代码中判断IE页面文档初始化完毕时会向页面中写入一个
1: <script id="__ie_onload" defer src="javascript:void(0)"></script>
而绑定多个函数时,就会向页面中写入多个上面的标签,由于页面上只允许一个id存在,所以通过document.getElementById(“__ie_onload”);自然就只能获得一个对象,所以只会执行一个绑定函数了。
我对其稍微进行了改造,无非就是加个数值变量,每次写入时让ID不同。
1: var _flag=1;//标志整型变量
2: function documentReady(fun){
3: if (document.addEventListener) {
4: document.addEventListener("DOMContentLoaded", fun, false);
5: } else if (/MSIE/i.test(navigator.userAgent)) {
6: document.write('<script id=__ie_onload'+_flag+' defer src=//0><\/script>');
7: var script = document.getElementById("__ie_onload"+_flag);_flag++;
8: script.onreadystatechange = function() {
9: if (this.readyState == 'complete') {
10: fun();
11: }
12: }
13: } else if (/WebKit/i.test(navigator.userAgent)) {
14: var _timer = setInterval( function() {
15: if (/loaded|complete/.test(document.readyState)) {
16: clearInterval(_timer);
17: fun();
18: }
19: }, 10);
20: }
21: }
这样如果绑定多个函数,就会在页面上依次写入id=”__ie_onload1”、id=”__ie_onload2”。。。等<script>标签了。
如此一来,即使绑定多个函数也可以顺利执行了。
后话:今天闲暇时,又上网搜了一下这方面的东西,发现早在06年就有人贴出了几近完美的解决方法,而且他们也考虑到了多个事件函数执行的问题,通过 栈结构解决的。它是将绑定的所有函数存入一个栈中,判断文档下载完成后,再从栈中依次取出函数执行,相比于我上面的方法,判断文档是否完成只需要一次即 可,无论绑定了多少个函数都是只需一次,而我的就需要多次了。看样子mg12看的书貌似是抄袭人家的,也不知为何进行了精简,结果还给精简错了,囧。。原 始网页在http://www.thefutureoftheweb.com/blog/adddomloadevent,有兴趣围观。
下面是文章的代码去掉注释后
1: addDOMLoadEvent = (function(){
2: var load_events = [],load_timer,script,done,exec,old_onload,
3: init = function () {
4: done = true;
5: clearInterval(load_timer);
6: while (exec = load_events.shift())
7: exec();
8: if (script) script.onreadystatechange = '';
9: };
10: return function (func) {
11: // if the init function was already ran, just run this function now and stop
12: if (done) return func();
13: if (!load_events[0]) {
14: // for Mozilla/Opera9
15: if (document.addEventListener)
16: document.addEventListener("DOMContentLoaded", init, false);
17: // for Internet Explorer
18: /*@cc_on @*/
19: /*@if (@_win32)*/
20: if (/MSIE/i.test(navigator.userAgent)){
21: document.write("<script id=__ie_onload defer src=//0><\/scr"+"ipt>");
22: script = document.getElementById("__ie_onload");
23: script.onreadystatechange = function() {
24: if (this.readyState == "complete")
25: init(); // call the onload handler
26: };
27: }
28: /*@end @*/
29: // for Safari
30: if (/WebKit/i.test(navigator.userAgent)) { // sniff
31: load_timer = setInterval(function() {
32: if (/loaded|complete/.test(document.readyState))
33: init(); // call the onload handler
34: }, 10);
35: }
36: old_onload = window.onload;
37: window.onload = function() {
38: init();
39: if (old_onload) old_onload();
40: };
41: }
42: load_events.push(func);
43: }
44: })();
具体的效果,可以到我的友情链接页面查看。这个页面有许多favicon图标需要从不同的地址下载,需要时间较长,而使用本文中的初始化装载方法后,并不会因为图标的未下载而影响我js代码的执行。大家可以很顺利的看到边栏各个栏目被加载上(注:边栏应用了延迟加载)。
第二个
jQuery 提供一个 $(document).ready(); 来解决此问题,当页面 DOM 加载完成后,ready() 里的函数便会立即执行,但如果我们在不使用 jQuery 的情况下呢?
好了,废话不多说,大家都明白想要什么,以下是来自国外网站的一段代码,功能同等于 jQuery 的 $(document).ready();
(function () {
var ie = !!(window.attachEvent && !window.opera);
var wk = /webkit\/(\d+)/i.test(navigator.userAgent) && (RegExp.$1 < 525);
var fn = [];
var run = function () { for (var i = 0; i < fn.length; i++) fn[i](); };
var d = document;
d.ready = function (f) {
if (!ie && !wk && d.addEventListener)
return d.addEventListener('DOMContentLoaded', f, false);
if (fn.push(f) > 1) return;
if (ie)
(function () {
try { d.documentElement.doScroll('left'); run(); }
catch (err) { setTimeout(arguments.callee, 0); }
})();
else if (wk)
var t = setInterval(function () {
if (/^(loaded|complete)$/.test(d.readyState))
clearInterval(t), run();
}, 0);
};
})();
把这段代码放到你的页面中或者某个导入的脚本中,然后你就可以像下面这样使用了:
document.ready(function(){
alert('Document is ready!');
});
打开页面,看到效果没?YES,这就是去掉了 $(); 的 document.ready(); 咱不再依赖 jQuery,但如果你的站点已经使用了 jQuery,就不用再多此一举了。
如果你并没有感觉到明显的区别,你可以到百度里随便便搜一张很大加载速度又慢的图片放到页面里试一试,打开一看,效果就很明显了。 下面还有一个来自于 PHPWind 论坛程序中某个脚本中的函数片段:
~function() {
var FNArray=[];
var D = document;
/**
* 使用举例:
window.onReady(FunctionName[,argu1,[argu2,[....]]]);
*/
window.onReady = function(fallBackFunction) {
var argu=[];
for(var i=1,len=arguments.length; i<len; i++) {
argu.push(arguments[i]);
}
var is_ie = !!(window.attachEvent && !window.opera); //增加的
if(window.readyBound) return fallBackFunction.apply(this,argu);
if(!is_ie) return fallBackFunction.apply(this,argu);
FNArray.push(fallBackFunction);
readyBound = true;
var ready = 0;
//Mozilla, Opera and webkit nightlies currently support this event
if(D.addEventListener) {
//Use the handy event callback
D.addEventListener("DOMContentLoaded", function() {
D.removeEventListener("DOMContentLoaded", arguments.callee, false);
if(ready) return;
ready = 1;
for(var i=0,len=FNArray.length; i<len; i++) {
FNArray[i] ? FNArray[i].apply(this,argu) : 0;
}
}, false);
// If IE event model is used
} else if(D.attachEvent) {
// ensure firing before onload,
// maybe late but safe also for iframes
D.attachEvent("onreadystatechange", function() {
if(D.readyState === "complete") {
D.detachEvent("onreadystatechange", arguments.callee);
if (ready) return;
ready = 1;
for(var i=0,len=FNArray.length; i<len; i++) {
FNArray[i] ? FNArray[i].apply(this,argu) : 0;
}
}
});
// If IE and not an iframe
// continually check to see if the D is ready
if(D.documentElement.doScroll && window == window.top)(function() {
if(ready) return;
try {
// If IE is used, use the trick by Diego Perini
// http://javascript.nwbox.com/IEContentLoaded/
D.documentElement.doScroll("left");
} catch(error) {
setTimeout(arguments.callee, 0);
return;
}
ready = 1;
for (var i=0,len=FNArray.length; i<len; i++) {
FNArray[i] ? FNArray[i].apply(this,argu) : 0;
}
})();
}
};
}();
这段的用法是:
window.onReady(function(){
alert('Bingo~!');
});
两个原理相同,唯一不同的区别是 window.onReady() 除了第一个参数可以传递要执行的函数外,还可以在后面的参数传递要传给第一个参数函数中的参数...额,前面这句话怎么这么别扭呢..意思就是 onReady 第一个参数之后的参数都将作为另外的参数传递给第一个参数的函数..好像还有点别扭??不管了,你自己理解了,总之这个传参的功能用的不多。
window.onReady(); 的传参功能实际用处不多,实现方法也是一样,代码这么多,当然还是推荐使用 document.ready();
其实 window.onload 也并非是一无用处,很多情况下一些B/S软件需要页面全部加载后才提供用户相关功能,这样 window.onload 就可以提供一种“加载中”的功能,又或者是页面内容很少,完全无需 document.ready(); 根据各种情况,应该合理的使用 onload 和 ready。