domReady机制是很多框架和库都具有的种子模块,使用了在DOM树解析完成后就立即响应,不用等待图片等资源下载完成(onload执行时候表示这些资源完全下载完成)的一种机制,那怎么实现呢。
1)支持DOMContentLoaded事件的,就使用DOMContentLoaded事件;
2)不支持的,就用来自Diego Perini发现的著名Hack兼容。兼容原理大概就是,通过IE中的document.documentElement.doScroll(‘left’)来判断DOM树是否创建完毕或者使用监控script标签的onreadystatechange得到它的readyState属性判断【遗憾的是经过我们的实验,在IE下domReady机制总会在onload后执行】
1. domReady机制在IE7-8下
1.1 domReady机制源码(包括IE/非IE)
demo1.html1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<html lang="en">
<head>
<meta charset="UTF-8">
<title> DOMContentLoaded Demo</title>
</head>
<body>
<div id="div1"></div>
<script defer='defer' src="deferjs.js"></script>
<script src="http://cdn.bootcss.com/jquery/1.12.4/jquery.js"></script>
<script type="text/javascript" src="demo1.js"></script>
<script>
dom.Ready(function() {
console.info("我的domReady1");
});
</script>
</body>
</html>
deferjs.js1
console.log("defer script");
demo1.js1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68dom = [];
dom.isReady = false;
dom.isFunction = function(obj) {
return Object.prototype.toString.call(obj) === "[object Function]";
};
dom.Ready = function(fn) {
dom.initReady(); //如果没有建成DOM树,则走第二步,存储起来一起杀
if (dom.isFunction(fn)) {
if (dom.isReady) {
fn(); //如果已经建成DOM,则来一个杀一个
} else {
dom.push(fn); //存储加载事件
}
}
};
dom.fireReady = function() {
if (dom.isReady) return;
dom.isReady = true;
for (var i = 0, n = dom.length; i < n; i++) {
var fn = dom[i];
fn();
}
dom.length = 0; //清空事件
};
dom.initReady = function() {
if (document.addEventListener) {//非IE
document.addEventListener("DOMContentLoaded", function() {
console.log("DOMContentLoaded");
document.removeEventListener("DOMContentLoaded", arguments.callee, false); //清除加载函数
dom.fireReady();
}, false);
} else {//IE走这条线
if (document.getElementById) {
document.write("<script id=\"ie-domReady\" defer='defer'src=\"//:\"><\/script>");
document.getElementById("ie-domReady").onreadystatechange = function() {
console.log(this.readyState);
if (this.readyState === "complete") {
//只针对IE readyState 的值 complete--脚本执行完成。
//这个时候DOM树肯定已经解析完成了,不支持defer属性
//会在onload函数之后执行。
dom.fireReady();
console.log('this.readyState === "complete"');
this.onreadystatechange = null;
this.parentNode.removeChild(this);
}
};
}
}
};
/**********测试**************************************************/
dom.Ready(function() {
console.info("我的domReady2");
});
/*$(document).ready(function() {
dom.Ready(function() {
console.info("我的domReady4在jquery的ready函数中");
});
console.log('jquery中的ready函数');
});*/
dom.Ready(function() {
console.info("我的domReady3");
});
console.log('在js中');
window.onload = function(){
console.log("onload函数");
};
1.2 背景知识介绍
document.readystate
readyState 属性返回当前文档的状态(载入中……)。
该属性返回以下值:
- uninitialized - 还未开始载入
- loading - 载入中
- interactive - 已加载,文档与用户可以开始交互并引发DOMContentLoaded事件
- complete - 载入完成
- IE的 script的readyState
FireFox的script 元素不支持onreadystatechange事件,只支持onload事件
IE的 script 元素支持onreadystatechange事件,不支持onload事件
只针对IE readyState 的值 可能为 以下几个 :
- “uninitialized” – 原始状态
- “loading” – 下载数据中..
- “loaded” – 下载完成
- “interactive” – 还未执行完毕.
- “complete” – 脚本执行完毕
- defer和onload函数
1 | <script defer='defer' src="deferjs.js"></script> |
defer 属性仅适用于外部脚本(只有在使用 src 属性时)
- 如果 async=”async”:脚本相对于页面的其余部分异步地执行(当页面继续进行解析时,脚本将被执行)
- 如果不使用 async 且 defer=”defer”:脚本将在页面完成解析时执行
- 如果既不使用 async 也不使用 defer:在浏览器继续解析页面之前,立即读取并执行脚本
1.3 结果分析
在IE7/8打印的结果是:
dom.fireReady函数在onload函数之后执行
1.4 IE下监控DOM树是否解析完成的其他做法
除了使用document.write("<script id=\"ie-domReady\" defer='defer'src=\"//:\"><\/script>")
还可以监控DOM树是否解析完成
在更早的IE版本中,可以通过每隔一段时间执行一次document.documentElement.doScroll("left")来检测这一状态,
因为这条代码在DOM加载完毕之前执行时会抛出错误(throw an error)1
2
3
4
5
6
7
8
9(function() {
try { //在DOM未建完之前调用元素的doScroll抛出错误
document.documentElement.doScroll('left');
} catch (e) { //延迟再试
setTimeout(arguments.callee, 50);
return;
}
init(); //没有错误则执行用户回调
})();
2. domReady机制在chrome中
将上面的demo2.js文件下注释的jquery的ready函数取消注释进行执行,得到结果是:
demo1.html中将script标签放入到<head>
得到的结果是一样的。
在chrome中的顺序是:
document.readyState
为loading
- jquery的ready函数外 (打印结果:在js中)
document.readyState
为interactive
【DOM解析完成】- 带defer的script (打印结果:defer script)
- jquery的ready函数里面 (打印结果:jquery中的ready函数)
- 监听DOMContentLoaded要执行的函数 dom.fireReady(打印结果:DOMContentLoaded和我是domReady系列)
document.readyState
为compelete
- onload函数(打印结果:onload函数)
3.总结
1,2区别:
- 带defer的script标签,IE8以下中不支持defer属性
- dom.fireReady在IE中的逻辑是在
document.readyState=="compelte"
后,会在onload函数之后紧接着执行,在chrome/Firfox的逻辑是在document.addEventListener("DOMContentLoaded",function(){})
的回掉函数中。
综上所诉,执行的顺序应该为:
document.readyState
为loading
- jquery的ready函数外
- 【非IE下】
document.readyState
为interactive
【DOM解析完成】- 带defer的script
- jquery的ready函数里面
- 触发
DOMContentLoaded
事件,监听DOMContentLoaded要执行的函数
- 【IE常用来判断DOM树是否解析完成】document.documentElement.doScroll 这时可以让HTML元素使用doScroll方法,抛出错误就是DOM树未解析完成
document.readyState
为compelete
- onload函数(打印结果:onload函数)【图片flash等资源都加载完毕】
最后附上监测IE,在IE的onload函数后面执行执行的另外一种实现方式
1 | //http://javascript.nwbox.com/IEContentLoaded/ |
参考阅读: