两个概念
- DOM就绪:指浏览器已经接收到整个HTML并且DOM解析完成,这时就可以开始操作dom了,如绑定事件。
- 渲染结束:浏览器已经接收到HTML中引用的所有样式文件、图片文件、以及Iframe等资源并渲染结束。
DOMContentLoaded
执行次序
DOMContentLoaded -> angular启动 -> onload
JQuery中这两个方法就是对DOMContentLoaded的监听
$(document).ready(callback) $(function(){})
当页面处于“DOM就绪”状态时,就会执行DOMContentLoaded回调,通常推荐在DOMContentLoaded事件触发的时候为DOM元素注册事件。
测试发现
可以认为dom就绪时,所有script也肯定已经就绪,script就绪意味着里面的同步脚本全都已经执行完毕。也就是说当DOMContendLoaded执行的时候,所有脚本(包括动态插入)中的同步代码都已经执行完了
注意:IE9以上才支持DOMContentLoaded
而对于IE6、7、8,如何监听DOM就绪这个事件呢?
ps: IE中使用attachEvent来绑定事件,而其他浏览器不支持这个方法,使用addEventListener来绑定事件
1.setTimeout
setTimeout(function(){ console.log("in timeout 1") }); window.addEventListener("DOMContentLoaded",function(){ console.log("DOMContentLoaded"); }); setTimeout(function(){ console.log("in timeout 2") }); window.onload = function(){ console.log("onload"); };
有两种运行结果(分别是有大图片资源和无大图片资源情况下):
timeout中和onload中代码执行的次序不确定,当资源(如大图片)等会延迟onload的执行,但可以明确的是,timeout中的代码都是在dom就绪之后才执行
2.检测readyState为complete状态
setTimeout(function(){ console.log("in timeout 1") }); window.addEventListener("DOMContentLoaded",function(){ console.log("DOMContentLoaded"); }); setTimeout(function(){ console.log("in timeout 2") }); window.onload = function(){ console.log("onload"); }; document.onreadystatechange = function(){ console.log(document.readyState) }
两种运行结果(分别是无大图片资源和有大图片资源情况下):
可以发现规律:complete必定在DOMContentLoaded之后才执行,也就是说complete状态时,dom就已经就绪了
3.defer script标签
先了解一下defer和async,资料参考:defer和async的区别
我简单总结如下:
defer:html渲染完再执行,脚本间有序执行,能保证依赖关系
async:下载完就执行,不能保证有序,而且仅仅适用于外部脚本
以上两者都是异步下载,即下载过程不影响html的渲染。如果不指定以上的属性(没有其一),也就是默认情况下,script的下载和执行会阻塞后续的html渲染和脚本执行,或者可以简单理解为:默认情况下,script没下载执行完成前,整个浏览器就都什么都不做了【既不渲染页面也不执行其他脚本了,针对这一点的优化策略是要么把js放到底部,要么使用setTimeout把耗时的操作延迟执行,这样就能避免影响其他资源的下载和dom渲染了】
经过以上了解,可以发现,将script设置为defer,就可以保证该脚本在dom就绪后才执行了
测试发现:defer会在onload之前执行。而且使用以上两个属性后,script都是异步加载,异步加载的脚本中不允许出现document.write语句,否则报错:
Failed to execute 'write' on 'Document': It isn't possible to write into a document from an asynchronously-loaded external script unless it is explicitly opened.
而且在onload中执行document.write的话会完全覆盖原来的body内容。总结document.write的作用:onload前执行会往文档上添加内容,后执行会覆盖内容
补充onload 和 defer之间的关系:
https://stackoverflow.com/questions/34753567/defer-attribute-and-onload-event
https://stackoverflow.com/questions/5250412/how-exactly-does-script-defer-defer-work
onLoad
当页面所有元素都加载完毕后【所有脚本都加载和执行、图片加载完成】,才会触发onLoad。或者这个过程可以称为“渲染结束”
window.addEventListener("load", function(event) { console.log("All resources finished loading!"); });
onLoad回调通常被称为最后的回调
注意IE8及以下不支持addEventListener
,需要使用attachEvent
来绑定事件处理函数
指定onload方法有三种方式:
addEventListener("load",function(){
<body onload="o3()">
window.onload = function(){
谁先指定在前面,onload时就谁先执行。其中后两种方式如果同时指定的话,则先声明的会被后声明的覆盖
document.readyState
"loading"
:DOM在加载过程中;"interactive"
:DOM就绪但资源仍在加载中;"complete"
:DOM加载完成。
动态插入script
假设首页页面代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<script>
function sleep(milliSeconds){
let startTime = new Date().getTime();
while(new Date().getTime() < startTime + milliSeconds){}
}
function log(msg){
console.log(new Date().getSeconds() + "s - " + msg);
}
log("start")
</script>
<script src="1.js"></script>
<script src="2.js"></script>
</head>
<body>
</body>
</html>
1.js
sleep(3000);
2.js
document.write('<script src="3.js"></script>');
window.addEventListener("DOMContentLoaded",function(){
log("DOMContentLoaded");
});
window.onload = function(){
log("onload");
};
3.js
log('3.js开始执行');
sleep(3000);
运行结果:
生成的html如下:
结论:
sleep函数理解为 脚本中大量的耗时的同步操作
DOMContentLoaded 会等待js脚本【包括scipt和动态加入的script标签】执行完后再触发事件回调,具体是为什么,往回看defer那一节就知道了
关于默认script是阻塞的两个原因
1.脚本可能使用document.write来修改页面内容,因此浏览器会等待,以保证页面能正确布局
2.保证脚本能按顺序到达浏览,从而能按顺序来执行