浏览器在渲染页面时需要将 HTML 标记转化成 DOM 对象
CSS 则会被转化成 CSSOM 对象
DOM 和 CSSOM 是独立的树形结构,
(资料图)
当 DOM 树和 CSSOM 树都构建完成的时候,他们就会合并在一起构建 render tree,因为要在页面上渲染不仅需要这个页面的结构,也需要知道整个页面的样式,所以 render tree 是 DOM 树和 CSSOM 树的结合体,有了 render tree,浏览器才能知道把什么内容按照什么样式渲染在屏幕上。
浏览器从获取 HTML 到最终在屏幕上显示内容需要完成以下步骤:
处理 HTML 标记并构建 DOM 树。处理 CSS 标记并构建 CSSOM 树。将 DOM 与 CSSOM 合并成一个 render tree。根据渲染树来布局,以计算每个节点的几何信息。将各个节点绘制到屏幕上。经过以上整个流程我们才能看见屏幕上出现渲染的内容,优化关键渲染路径就是指最大限度缩短执行上述第 1 步至第 5 步耗费的总时间,让用户最快的看到首次渲染的内容。
另外,这是一个渐进的过程。为达到更好的用户体验,呈现引擎会力求尽快将内容显示在屏幕上。它不必等到整个 HTML 文档解析完毕之后,就会开始构建呈现树和设置布局。在不断接收和处理来自网络的其余内容的同时,呈现引擎会将部分内容解析并显示出来,因为 HTML 采用基于流的布局模型,这意味着大多数情况下只要一次遍历就能计算出几何信息。处于流中靠后位置元素通常不会影响靠前位置元素的几何特征,因此布局可以按从左至右、从上至下的顺序遍历文档。但是也有例外情况,比如 HTML 表格的计算就需要不止一次的遍历。
阻塞渲染的因素外部样式表从上面的整个流程我们已经知道,浏览器的渲染需要 render tree, render tree 需要 CSSOM 树才行,所以样式表的加载是会阻塞页面的渲染的,如果有一个外部的样式表处于下载中,那么即使 HTML 已经下载完毕,也会等待外部样式表下载并解析完毕才会开始构建 render tree。
脚本脚本就更麻烦了,先明确一点, JS 引擎和 UI 的渲染引擎是互斥的,所以当脚本在执行的时候浏览器要将控制权就给 JS 引擎,等到 JS 执行完毕再还给 UI 引擎,不论这个脚本是以何种形式加载的,在执行时均会阻塞 UI 的渲染。
接下来分别看不同形式加载的脚本对页面渲染的阻塞情况:
内联脚本<script>...</script>
内联的脚本随着 HTML 一起下载,在开始执行时已经完成了 字节 → 字符 → 令牌 → 节点 → 对象模型 的整个过程,所以不存在下载的时间(其实也不能这么说,下载的时间算在了 HTML 的下载时间中),执行时是会阻塞关键渲染路径的。
外部脚本<script src="sample.js"></script>
外部脚本的整个加载过程及执行过程都是阻塞关键渲染路径的。
带 defer 和 async 的外部脚本<script src="sample.js" defer></script><script src="sample.js" async></script>
带 defer/async 的脚本会与 HTML 并行下载,下载的过程不会阻塞 DOM 的构建,但是执行是会的,不同的是 defer 是在 DomContentLoaded 之前执行,async 是加载完之后立刻执行。
defer/async 的脚本在下载期间不会阻塞页面解析不是一个技术原因而是一个选择,因为内联脚本/外部脚本是要等待他们执行,所以不得不等待他们下载。而页面并不需要等待 defer/async 的脚本,所以他们的下载与页面的解析是并行的。
动态生成的脚本var dynamicScript = document.creatElement("script")dynamicScript.src = "sample.js"document.head.appendChild(dynamicScript)dynamicScript.onload = function(){...}动态生成的脚本的下载过程不会阻塞页面的解析,执行会阻塞解析,有点 async 的感觉。
脚本与样式表的依赖关系脚本不仅能够访问 DOM 元素,还能访问 DOM 的样式,如果将要执行脚本时浏览器尚未完成 CSSOM 的下载及构建,浏览器将延迟脚本执行和 DOM 构建,直至其完成 CSSOM 的下载和构建。
所以,CSSOM 的构建会阻塞 HTML 的渲染,也会阻塞 JS 的执行,JS 的下载与执行(内联及外部样式表)也会阻塞 HTML 的渲染。
优化方法为尽快完成首次渲染,我们需要最大限度减小以下三种可变因素:
关键资源的数量:可能阻止网页首次渲染的资源。关键路径长度:获取所有关键资源所需的往返次数或总时间。关键字节的数量:实现网页首次渲染所需的总字节数,它是所有关键资源传送文件大小的总和。我们包含单个 HTML 页面的第一个示例包含一项关键资源(HTML 文档);关键路径长度也与 1 次网络往返相等(假设文件较小),而总关键字节数正好是 HTML 文档本身的传送大小。优化关键渲染路径的常规步骤如下:
对关键路径进行分析和特性描述:资源数、字节数、长度。最大限度减少关键资源的数量:删除它们,延迟它们的下载,将它们标记为异步等。优化关键字节数以缩短下载时间(往返次数)。优化其余关键资源的加载顺序:您需要尽早下载所有关键资产,以缩短关键路径长度。关键 CSS上面已经分析过了,样式表会阻塞渲染,在加载完毕之前是不会显示的,为了让用户以最快的速度看到页面上的内容,可以将页面的某一部分的样式抽离出来,单独放在一个样式表中或者内联在页面中,这样的样式称为关键样式,这部分样式会优先它可以是页面的骨架屏或者是用户刚加载进页面时看到的首屏的内容。
<script> loadCSS("non-critical.css"); </script> ...body goes here