说在前面
在很久很久以前,我在封装自己的JQuery库时就使用过DOMContentLoaded,觉得这个知识点看看别的文章就行了,不过现在我想把它记下来。
JS代码与body标签的位置关系
一个HTML初学时会遇到的问题,一个html页面中js代码应该放到哪里?
代码语言:javascript复制<!--如果script标签在body前面-->
<head>
...
<script>
var box = document.getElementById('box');
console.log(box) //null
</script>
</head>
<body>
<div id="box"></div>
</body>
---------
<!--如果script标签在body后面(里面的最后也可以)-->
<head></head>
<body>
<div id="box"></div>
<script>
var box = document.getElementById('box');
console.log(box) //div#box
</script>
</body>
Jetbrains全家桶1年46,售后保障稳定
上面代码可以看出,如果js代码写在body标签前面,而且没用其他事件而直接获取dom的话是无法获取的。而js代码写在body标签的后面(里面的最后也可以)就可以获取dom。
实际上如果了解浏览器解析HTML规则就很清楚原因了,浏览器解析HTML由上往下依次执行,如果遇到<script>会阻塞解析,先执行该JS脚本(如果是外部JS文件还要先加载),执行结束后再接着往下解析,所以上面获取不到dom的原因是当时JS代码执行时页面DOM树尚未构建完成。具体分析往下看。
script标签的defer和async
从上面知道,浏览器解析HTML遇到script标签会阻塞。上面举例的JS代码都是内嵌在HTML中的,这样再解析到script时直接执行就行。但如果是引入外部JS文件的话会有一点不同,要先加载该JS文件,然后执行,然后在往下解析HTML。但script标签上还有两个常见属性defer和async
一般情况<script src=”xxx.js”>
当浏览器遇到 script 标签时,文档的解析将停止,并立即下载并执行脚本,脚本执行完毕后将继续解析文档。
defer <script defer src=”xxx.js”>
当浏览器遇到 script 标签时,文档的解析不会停止,JS文件的加载与文档解析并行(异步),待到文档解析DOM构建完成,脚本才会执行(在DOMContentLoaded事件触发之前)。
async <script async src=”xxx.js”>
当浏览器遇到 script 标签时,文档的解析不会停止,JS文件的加载与文档解析并行(异步),脚本下载完成后开始执行脚本,脚本执行时文档会停止解析。
看图(图片来源于网络)
蓝色线代表网络读取,红色线代表执行时间,这俩都是针对脚本的;绿色线代表 HTML 解析。
总结defer和async的区别:
- 加载时是一样的,相对于HTML解析是异步的。
- 不同的是执行时机,async在代码加载完之后会马上执行,并且执行时会阻塞HTML解析。而defer则要等到文档解析DOM构建完成,DOMContentLoaded事件触发之前执行。
- async执行时机不确定性,要注意使用场景。
所以script标签加上defer属性,即使不用DOMContentLoaded或window.onload也可以获取操作DOM。
代码语言:javascript复制//index.js
var box = document.getElementById('box');
//可以获取到div
console.log(box); //div#box
--------
<head>
...
<script defer src="./index.js"></script>
</head>
<body>
<div id="box"></div>
</body>
DOMContentLoaded和window.onload
DomContentLoaded
MDN解释:当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完成加载。另一个不同的事件 load 应该仅用于检测一个完全加载的页面。 在使用 DOMContentLoaded 更加合适的情况下使用 load 是一个令人难以置信的流行的错误,所以要谨慎。
注意:DOMContentLoaded 事件必须等待其所属script之前的样式表加载解析完成才会触发。
代码语言:javascript复制//index.css
#box{
width:100px;
height:100px;
background: pink;
}
---------
//DOMContentLoaded前面引入样式表
<head>
<link rel="stylesheet" href="./index.css"/>
<script>
function getStyle(el, attr) {
return el.currentStyle ? el.currentStyle[attr] : getComputedStyle(el)[attr];
}
// DOMContentLoaded
document.addEventListener('DOMContentLoaded', function(){
var box = document.getElementById('box');
//可以获取到样式表的样式
console.log(getStyle(box, 'width')); //100px
})
</script>
</head>
<body>
<div id="box"></div>
</body>
--------
//DOMContentLoaded后面引入样式表
<head>
<script>
function getStyle(el, attr) {
return el.currentStyle ? el.currentStyle[attr] : getComputedStyle(el)[attr];
}
// DOMContentLoaded
document.addEventListener('DOMContentLoaded', function(){
var box = document.getElementById('box');
//可能无法获取到样式表的样式
console.log(getStyle(box, 'width')); //1350px或100px
})
</script>
<link rel="stylesheet" href="./index.css"/>
</head>
<body>
<div id="box"></div>
</body>
上面代码看出,在DOMContentLoaded后面引入样式表,DOMContentLoaded可能无法获取样式表里的样式,因为此时HTML解析完成,DOM树构建完成,但外部css文件可能还没加载完成。
暂时得出结论:js代码应该放在样式表之后。
window.onload
这个就没什么好说的,此时HTML文档解析完成,其他依赖资源也全部加载完成。
用document.readyState看一下各种情况下的HTML文档状态:
代码语言:javascript复制<script>
window.onload = function(){
console.log('window.onload', document.readyState);
}
document.addEventListener('DOMContentLoaded', function(){
console.log('DOMCOntentLoaded', document.readyState);
}, false);
console.log(document.readyState);
</script>
//输出
//loading 文档加载中
//DOMCOntentLoaded interactive 文档与用户可以开始交互,可以操作DOM
//window.onload complete 一切完成
ready()简单实现
JQuery中有个$(document).ready(),下面就简单实现一下。
代码语言:javascript复制document.ready = function(fn){
if(document.addEventListener){ //现代浏览器
document.addEventListener('DOMContentLoaded', function(){
document.removeEventListener('DOMContentLoaded', arguments.callee, false);
fn();
}, false);
}else if(document.attachEvent){ //低版本IE
document.attachEvent('onreadystatechange', function(){
if(document.readyState === 'complete'){
document.detachEvent('onreadystatechange', arguments.callee);
fn();
}
})
}else{
window.onload = fn;
}
}
总结
HTML文档加载步骤:
- 由上往下解析HTML结构。
- 遇到src属性则发起请求加载资源,只有script会阻塞HTML解析,其他(css、img等)都不会影响HTML解析。
- script资源加载完,执行JS脚本。
- DOM树构建完成,触发DOMContentLoaded
- 其他css、img、iframe等资源如果还未加载完成继续加载。
- 页面加载完毕,触发window.onload
为什么要强调css放头部,js放尾部
因为css样式表是浏览器渲染页面的重要一环,应该尽早发起请求加载,毕竟也不会阻塞HTML解析。
而HTML解析遇到script会阻塞,所以应该放到后面,而不阻塞其他资源请求。虽然说还是要等script加载执行完成之后才会触发DOMContentLoaded,但现在很多现代浏览器为了更好地用户体验,能够渲染不完整的dom树和cssom,尽快的减少白屏的时间。
参考文章 参考文章
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/234712.html原文链接:https://javaforall.cn