在前端性能优化树上有不少值得展开的话题,从输入 URL 到页面加载完成发生了什么 这一道经典的面试题就涉及到不少内容,但前端主要关注的部分仍是 浏览器解析响应的内容并渲染展现给用户 这一步,本文将会详细分析这一步的具体过程并在分析的过程当中理解该如何作性能优化。css
首先介绍一个名词 CRP,即 关键渲染路径 (Critical Rendering Path)(后文统一以 CRP 指代):html
关键渲染路径是浏览器将 HTML CSS JavaScript 转换为在屏幕上呈现的像素内容所经历的一系列步骤。前端
当咱们请求某个 URL 之后,浏览器得到响应的数据并将全部的标记转换到咱们在屏幕上所看到的 HTML
,有没有想过这中间发生了什么?git
浏览器会遵循定义好的完善步骤,从处理 HTML 和构建 DOM 开始:github
StartTag: HTML
StartTag:head
Tag: meta
EndTag: head
这样的令牌 ,整个浏览由令牌生成器来完成。HTML
head
这些节点对象,起始和结束令牌代表了节点之间的关系。DOM 是一个树结构,表示了 HTML 的内容和属性以及各个节点之间的关系。web
好比如下代码:面试
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="style.css" rel="stylesheet">
<title>Critical Path</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg"></div>
</body>
</html>
复制代码
最终就转成下面的 DOM 树:浏览器
浏览器如今有了页面的内容,那么该如何展现这个页面自己呢?缓存
与转换 HTML 相似,浏览器首先会识别 CSS 正确的令牌,而后将这些令牌转成 CSS 节点,子节点会继承父节点的样式规则,这就是层叠规则和层叠样式表。性能优化
好比上面的 HTML 代码有如下的 CSS :
body { font-size: 16px }
p { font-weight: bold }
span { color: red }
p span { display: none }
img { float: right }
复制代码
最终就转成下面的 CSSOM 树:
这里须要特别区分的是,DOM 树会逐步构建来使页面更快地呈现,可是 CSSOM 树构建时会阻止页面呈现。
缘由很简单,若是 CSSOM 树也能够逐步呈现页面的话,那么以后新生成的子节点样式规则有可能会覆盖以前的规则,这就会致使页面的错误渲染。
让咱们来作一个思考题,请看如下的 HTML 代码:
<div>
<h1>H1 title</h1>
<p>Lorem...</p>
</div>
复制代码
对于如下两个样式规则,哪一个样式规则会渲染得更快?
h1 { font-size: 16px }
div p { font-size: 12px }
复制代码
直觉上很容易以为第二个规则是更具体的,应该会渲染更快,但实际上偏偏相反:
那么到如今为止,DOM 树包含了页面的全部内容,CSSOM 树包含了页面的全部样式,接下来如何将内容和样式转成像素显示到屏幕上呢?
浏览器会从 DOM 树的根部开始看有没有相符的 CSS 规则,若是有的话就将节点和样式复制到渲染树上,没有的话就只将节点复制过来,而后继续向下遍历。
特别要注意的是,渲染树最重要的特性是只捕获可见内容 :
display: none
,表示这个节点不该该呈现,则这个节点和其子项都会直接跳过。好比如下将 DOM 树和 CSSOM 树合并成渲染树的结果:
如今咱们已经有了渲染树,接下来要作的是肯定元素在页面上的位置。
咱们考虑如下的代码:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Critial Path: Hello world!</title>
</head>
<body>
<div style="width: 50%">
<div style="width: 50%">Hello world!</div>
</div>
</body>
</html>
复制代码
浏览器在渲染时会将这里父 div 的宽度设置成 body 的 50%,将子 div 的宽度设成父 div 的 50%,那么这里 body 的宽度是如何肯定的?
注意咱们在 meta 标签中设置了一行代码:
<meta name="viewport" content="width=device-width,initial-scale=1">
复制代码
咱们在实际进行自适应网页设计时都会加上这行代码表示布局视口的宽度等于设备的宽度,所以呈现出来就是这样:
最后一步就是将全部准备好的内容 绘制 到页面上。
任什么时候候咱们想要更新渲染树时,可能都会从新进行布局和绘制这一过程,浏览器自己会采起各类智能的功能尝试从新绘制最低请求区域,但具体仍是取决于咱们向渲染树应用了哪一种类型的更新。
在谈优化以前,咱们先定义一下用来描述 CRP 的词汇:
- 关键资源: 可能阻止网页首次渲染的资源。
- 关键路径长度: 获取全部关键资源所需的往返次数或总时间。
- 关键字节: 实现网页首次渲染所需的总字节数,等同于全部关键资源传送文件大小的总和。
结合咱们谈过的步骤,咱们着重会考虑的优化策略是在合成渲染树以前。
首先咱们能够优化 DOM,具体体如今如下几步:
而后是优化 CSSOM,缩小、压缩以及缓存一样重要,对于 CSSOM 咱们前面重点提过了它会阻止页面呈现,所以咱们能够从这方面考虑去优化,让咱们看下面的代码:
body { font-size: 16px }
@media screen and (orientation: landscape) {
.menu { float: right }
}
@media print {
body { font-size: 12px }
}
复制代码
当浏览器遇到 CSS 时,会阻止呈现页面直到 CSSOM 解析完毕,可是对于一些特定场合才会运用的 CSS (好比上面两个媒体查询),浏览器会依旧请求,但不会阻塞渲染了,这也是为何咱们有时会将 CSS 文件拆分到不一样的文件,上面的样式表声明能够优化成这样:
<link href="style.css" rel="stylesheet">
<link href="landscape.css" rel="stylesheet" media="orientation:landscape">
<link href="print.css" rel="stylesheet" media="print">
复制代码
当咱们用 PageSpeed Insights 检测咱们的网站时,常常出现的一条就是 建议减小关键 CSS 元素数量 。
Google 官方文档 也建议: 当咱们声明样式表时,请密切关注媒体查询的类型,它们极大地影响了 CRP 的性能 。
接下来让咱们考虑 JavaScript
外部依赖能够优化的地方,再看下面的代码:
<p>
Awesome page
<script src="write.js"></script>
is awesome
</p>
复制代码
当浏览器遇到 script 标记时,会阻止解析器继续操做,直到 CSSOM 构建完毕,JavaScript
才会运行并继续完成 DOM 构建过程,对于 JavaScript
依赖的优化,咱们最经常使用的一种方法是当网页加载完成,浏览器发出 onload 事件后再去执行脚本(或者直接放在底部),但实际上还有更简单的策略:
async
: 当咱们在 script 标记添加 async
属性之后,浏览器遇到这个 script 标记时会继续解析 DOM,同时脚本也不会被 CSSOM 阻止,即不会阻止 CRP。defer
: 与 async
的区别在于,脚本须要等到文档解析后( DOMContentLoaded
事件前)执行,而 async
容许脚本在文档解析时位于后台运行(二者下载的过程不会阻塞 DOM,但执行会)。async
。这里给出一个参考图:
浏览器还有一个特殊的流程,叫作预加载扫描器,它会提早扫描文档并发现关键的 CSS 和 JS 资源来下载,这个过程不会阻塞渲染,想详细了解它的原理能够浏览这篇文章 How the Browser Pre-loader Makes Pages Load Faster,实际的应用可浏览 前端性能优化之关键路径渲染优化
总结一下,为了首屏最快地渲染,咱们一般会采起下列步骤:
更详细的优化建议能够阅读 PageSpeed Rules and Recommendations