这篇文章是第二篇,第一篇在这里。javascript
第一篇大体讲到了浏览器从获取原始数据开始,直到把内容画到屏幕上,可是尚未完。css
以后,还有第三篇(整理)在这里html
当你听到“渲染阻塞”的时候,你会想到啥?我猜是,‘某些行为阻止了浏览器把内容画到屏幕上’。java
的确是你猜的那样。浏览器
因此这里有了咱们的第一个优化点,把最重要的HTML内容和CSS样式提供给客户端,越快越好。服务器
DOM和CSSOM都必须在屏幕绘制以前构造完成,因此HTML
和CSS
都是渲染阻塞资源。网络
因此关键点就是你应该让客户端竟可能快地获取你的html
和css
,这样就可以优化首屏渲染的时间。app
如今,基本只要是个网站都有JavaScript。。。async
JavaScript是会修改页面内容以及样式的。做为实现,你能用JS从DOM树里添加删除元素,还可以修改元素的CSSOM的属性。post
这很好,可是这也须要付出代价。
考虑一下下面的HTML
文档:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Medium Article Demo</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<p id="header">How Browser Rendering Works</p>
<div><img src="https://i.imgur.com/jDq3k3r.jpg"></div>
</body>
</html>
复制代码
这是个很简单的例子,style.css
内容也很简单,以下:
body {
background: #8cacea;
}
复制代码
最终显示结果以下:
简单文本和图片渲染到了屏幕上。
从以前的步骤来看,通过 原始数据->字符->标记 转换后,浏览器一旦读到<link rel="stylesheet" href="style.css">
这一行的时候,就回去请求这个css文件style.css
,DOM的构建仍然继续,而且一旦css文件返回内容以后,这个CSSOM的构建也开始了。
当JavaScript来了以后,这个过程会发生什么样的变化呢?
记住一点,只要浏览器读到了script
标签,那DOM的构建就会暂停!
整棵DOM树的建立过程会暂停,直到这个script运行完成。(这里不是指那些ready以后的脚本哈。)
由于js会去修改DOM和CSSOM,而浏览器又不可以肯定这个js会作些什么,因此须要经过暂停整个DOM的建立来预防。
这样会有多糟?
让咱们用上面那个例子,而后加一点基础的script
进去:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Medium Article Demo</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<p id="header">How Browser Rendering Works</p>
<div><img src="https://i.imgur.com/jDq3k3r.jpg"></div>
<script> let header = document.getElementById("header"); console.log("header is: ", header); </script>
</body>
</html>
复制代码
在这个script里,我用id去Dom里获得一个节点header
,而后打印到了console。
你有注意到这个script
是在body
的底部么,让咱们看下若是把它放在头部会怎样:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Medium Article Demo</title>
<link rel="stylesheet" href="style.css">
<script> let header = document.getElementById("header"); console.log("header is: ", header); </script>
</head>
<body>
<p id="header">How Browser Rendering Works</p>
<div><img src="https://i.imgur.com/jDq3k3r.jpg"></div>
</body>
</html>
复制代码
运行后,获得的结果header是null
为何?
其实很简单。当HTML解析器正在建立DOM树的时候,发现有一个script
标签,而此时,body
标签以及它的内容尚未解析出来。DOM构建被暂停直到这个script执行完。
这里又带出了另外一个重要的点。
你脚本的位置很重要。
若是你以文件形式加载一个js,同样也会暂停DOM的建立,相似:<script src="app.js"></script>
若是这个app.js
放在远程服务器上,而且网络很慢,须要加载3秒呢?那么,DOM的构建就要等到3秒以后再继续!!!
这是个很大的性能问题,但还不是所有。
记得JS还可以改变CSSOM,好比:
document.getElementsByTagName("body")[0].style.backgroundColor = "red";
那么,在CSSOM建立好以前,解析器读到script
会怎样呢?
结果是js的执行将会暂停,直到CSSOM建立好。
因此当遇到script
的时候,DOM的构建会中止,可是CSSOM并不会(【译注】不但不会,还会暂停js的执行)。
【译注】这里我想要补充一下,经过上面所说的,咱们能够推出一个观点:
JS 文件不仅是阻塞 DOM 的构建,它会致使 CSSOM 也阻塞 DOM 的构建。
本来 DOM 和 CSSOM 的构建是互不影响,井水不犯河水,可是一旦引入了 JavaScript,CSSOM 也开始阻塞 DOM 的构建。由于不完整的 CSSOM 是没法使用的,若是 JavaScript 想访问 CSSOM 并更改它,那么在执行 JavaScript 时,必需要能拿到完整的 CSSOM。因此就致使了一个现象,若是浏览器还没有完成 CSSOM 的下载和构建,而咱们却想在此时运行脚本,那么浏览器将延迟脚本执行和 DOM 构建,直到完成 CSSOM 的建立。也就是说,在这种状况下,浏览器会先去构建 CSSOM,而后再执行 JavaScript,最后在继续构建 DOM。
默认状况下,每一个script都会组织DOM树的建立。不够有一个方法可以改变这一个行为。
若是你在script
标签里加一个async
属性,那么DOM树的建立就不会暂停,这个脚本就会在下载完后去执行。例如:
<script src="https://some-link-to-app.js" async></script>
咱们以前所说的从获取html,css和js原始数据开始一直到画到屏幕上。这整个过程就被叫作关键渲染路径(critical rendering path)。
优化网页性能就是优化这条关键渲染路径。
通过良好优化的网站应该是渐进式加载的,而不会被整个阻止。
这就是一个网站快与慢的差别所在。 一个比较好的策略是让浏览器优先加载哪些资源,加载资源的顺序比较重要。(【译注】好比:大部分都会将 js 放在 底部,css 放在顶部等。)