Skip to main content

HTML资源加载顺序

引言

css和js是否会阻塞页面的解析和渲染?

CSS加载会阻塞DOM树解析和渲染吗?

网页资源加载顺序测试
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>资源加载顺序</title>
<style>
h1{
color: red !important;
}
</style>
<link href="https://cdn.bootcss.com/bootstrap/4.0.0-alpha.6/css/bootstrap.css" rel="stylesheet">
<script>
function h(){
console.log(document.querySelector('h1'))
}
setTimeout(h, 0)
</script>
</head>
<body>
<h1>这是红色的</h1>

</body>
</html>

假设CSS会阻塞DOM树解析和渲染,那么会出现以下情况:

  • 在bootstrap.css还没加载完成之前,下面的内容不会被解析渲染,那么一开始看到的应该是白屏,h1不会显示出来,并且此时console.log的结果应该是一个空数组

CSS会阻塞DOM树解析?

打开浏览器,发现在bootstrap.css还没加载完成的时候,h1并没有显示,但是此时控制台输出h1可以得知:

DOM树至少已经解析完成到了h1,而此时css还没加载完成,也就说明CSS并不会阻塞DOM的解析

CSS加载会阻塞DOM树渲染吗?

如上操作可以得到结论,css未加载完成之前,页面都是处于白屏状态(根据浏览器机制如果已经有存在的页面那么页面会在下一个完成加载才会显示出),css加载完成之后,红色字体才显示出来,DOM中的元素虽然已经完成解析,但是并没有被渲染出来。所以CSS加载会阻塞DOM树渲染

CSS加载会阻塞JS运行吗?

CSS加载阻塞JS运行
<!DOCTYPE html>
<html lang="en">

<head>
<title>css阻塞</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script>
console.log('before css')
var startDate = new Date()
</script>
<link href="https://cdn.bootcss.com/bootstrap/4.0.0-alpha.6/css/bootstrap.css" rel="stylesheet">
</head>

<body>
<h1>这是红色的</h1>
<script>
var endDate = new Date()
console.log('after css')
console.log('经过了' + (endDate - startDate) + 'ms')
</script>
</body>

</html>

假设:CSS会阻塞后面的JS运行,在link后面的js代码,应该在CSS加载完成之后才会输出。

但是经过运行发现,CSS前的JS先执行,位于 CSS后面的JS需要等CSS加载完成之后才执行,并经过169ms才执行,所以得出结论CSS会阻塞后面JS的执行

JS和DOM解析和渲染

JS 阻塞 DOM 解析

const arr = [];
for (let i = 0; i < 10000000; i++) {
arr.push(i);
arr.splice(i % 3, i % 7, i % 5);
}
const div = document.querySelector('div');
console.log(div);

浏览器转圈圈一会,这过程中不会有任何东西出现。之后打印出null,再出现一个浅绿色的div。现象就足以说明JS 阻塞 DOM 解析了。其实原因也很好理解,浏览器并不知道脚本的内容是什么,如果先行解析下面的DOM,万一脚本内全删了后面的DOM,浏览器就白干活了。更别谈丧心病狂的document.write。浏览器无法预估里面的内容,那就干脆全部停住,等脚本执行完再干活就好了。

对此的优化其实也很显而易见,具体分为两类。如果JS文件体积太大,同时你确定没必要阻塞DOM解析的话,不妨按需要加上defer或者async属性,此时脚本下载的过程中是不会阻塞DOM解析的。

而如果是文件执行时间太长,不妨分拆一下代码,不用立即执行的代码,可以使用一下以前的黑科技:setTimeout()。当然,现代的浏览器很聪明,它会“偷看”之后的DOM内容,碰到如link, scriptimg等标签时,它会帮助我们先行下载里面的资源,不会傻等到解析到那里时才下载。

浏览器遇到 script 标签时,会触发页面渲染

<body>
<div></div>
<script src="/js/sleep3000-logDiv.js"></script>
<style>
div {
background: lightgrey;
}
</style>
<script src="/js/sleep5000-logDiv.js"></script>
<link rel="stylesheet" href="/css/common.css">
</body>

其实这才是解释上面为何JS执行会等待CSS下载的原因:

答案是先浅绿色,再浅灰色,最后浅蓝色。由此可见,每次碰到script标签时,浏览器都会渲染一次页面。这是基于同样的理由,浏览器不知道脚本的内容,因而碰到脚本时,只好先渲染页面,确保脚本能获取到最新的DOM元素信息,尽管脚本可能不需要这些信息。

原理解析

浏览器的渲染机制分为一下几个步骤:

  1. 处理HTML并构建DOM树🌲
  2. 处理CSS构建CSSOM🌲
  3. 将DOM和CSSOM合并成一个渲染树🌲
  4. 根据渲染树来布局,计算每个节点的位置和样式
  5. 调用GPU绘制,合成图层,显式在屏幕

Locale Dropdown

从如上图片可以看出:

  1. DOM解析和CSS解析是两个并行的进程,它们分别由不同的解析引擎去解析,这也解释了为什么CSS的解析不会阻塞DOM的解析。
  2. 由于Render Tree是依赖DOM Tree和CSSOM Tree的,所以它必须等待到CSSOM Tree构建完成,也就是CSS资源加载完成(或者CSS资源加载失败)后,才能开始渲染,因此,CSS加载会阻塞DOM的渲染。
  3. 由于JS可能会操作之前的DOM节点和CSS样式,因此浏览器会维持html中css和js的顺序。因此样式表会在后面js执行前先加载执行完毕。所以CSS加载会阻塞后面JS的执行。

DOMContentLoaded

对于浏览器而言,页面加载主要有两个事件,一个是DOMContentLoaded,另一个是onLoad

onLoad
等待页面的所有资源都加载完成后才会触发,这些资源包括css、js、图片视频等。

DOMContentLoaded
当页面的内容解析完成后,则触发该事件

正如上面讨论过的,CSS会阻塞DOM的渲染和JS执行,而JS会阻塞DOM解析,那么可以做出如下两种假设:

  1. 当页面只存在CSS,或者JS都在CSS前面,那么DomContentLoaded不需要等到CSS加载完毕。
  2. 当页面同时存在css和js,并且js在css后面的时候,DomContentLoaded必须等到css和js都加载完毕才触发(为了防止一种bug,后面的js可能会获取和设置样式内容)

CSS不会阻塞DOM Ready

CSS不阻塞DOMReady
<!DOCTYPE html>
<html lang="en">
<head>
<title>css阻塞</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script>
document.addEventListener('DOMContentLoaded', function() {
console.log('DOMContentLoaded');
})
</script>
<link href="https://cdn.bootcss.com/bootstrap/4.0.0-alpha.6/css/bootstrap.css" rel="stylesheet">
</head>
<body>
</body>
</html>

可以看出,CSS还未加载完成,就已经出发了DOMContentLoaded回调,因为CSS后面没有任何JS代码

CSS会阻塞DOM Ready

CSS会阻塞DOMReady
<html lang="en">
<head>
<title>css阻塞</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script>
document.addEventListener('DOMContentLoaded', function() {
console.log('DOMContentLoaded');
})
</script>
<link href="https://cdn.bootcss.com/bootstrap/4.0.0-alpha.6/css/bootstrap.css" rel="stylesheet">

<script>
console.log('到我了没');
</script>
</head>
<body>
</body>
</html>

可以看到,只有在CSS加载完成后偶,才会触发DomContentLoaded回调,因为CSS后面有JS代码。

结论

  • CSS加载不会阻塞DOM🌲的解析。
  • CSS加载会阻塞DOM🌲的渲染。
  • CSS加载会阻塞后面JS语句的执行。
  • CSS后面没有JS时不会阻塞 DOM Ready。
  • CSS后面有JS时会影响DOM Ready。

所以script最好放底部,link最好放头部,如果头部同时有scriptlink的情况下,最好将script放在link上面