根据浏览器渲染流程研究“阻塞”

这篇文章源于公司内部的一次交流分享活动,选的主题都是些本身不了解的知识,而后经过学习研究再和同事分享。之因此选择本身不了解的知识,也是为了督促本身学习进步,而相应的因本人水平有限且初初接触,文章中可能会存在某些不严谨的或者不正确的地方,望各位大神批评指正。原本定的主题是“预加载”,由于不了解,因此一再追本溯源,最后竟成了这关于浏览器渲染与阻塞的文章。css

浏览器渲染流程

一、概述

浏览器有许多模块,其中负责呈现页面的是渲染引擎模块,下图为Webkit渲染流程:html


虽然不一样浏览器内核渲染细节有所不一样,但基本思路类似:node

  • 处理HTML标记并构建DOM树。
  • 处理CSS标记并构建CSSOM树。
  • DOM树与CSSOM树合并后造成渲染树Render Tree(只包含渲染网页所需的节点)。
  • layout布局(reflow自动重排)结算节点在设备视口内的确切位置和大小。
  • painting绘制(rasterizing栅格化):结合layout将渲染树中的各个节点绘制到屏幕上。

二、DOM和CSSOM树的构建

DOM和CSSOM是独立的数据结构,其解析是两个并行的进程,所以CSS加载不会阻塞DOM的解析。web

DOM树和CSSOM树的构建过程相似,下图为DOM树的构建过程:面试

字节 → 字符 → 令牌 → 节点 → 对象模型浏览器


  • 转换(字节->字符):将原始字节转换成字符;
  • 令牌化(字符->令牌):将字符转换成W3C HTML5标准规定的令牌,也就是标签,包括标签及标签内部字符串;
  • 词法分析(令牌->节点):根据令牌生成包含其属性和规则的“对象”;
  • DOM构建(节点->对象模型):节点间添加父子关系,造成树形结构。

三、Render树的生成

下图为渲染树的生成过程:bash


注意,渲染树只包含渲染网页所需的节点微信

CSS和JS阻塞

一、实验1——疑惑起

原文: zhuanlan.zhihu.com/p/24944905

用一句话归纳就是: JS 全阻塞,CSS 半阻塞。
数据结构

  1. JS 会阻塞后续 DOM 解析以及其它资源(如 CSS,JS 或图片资源)的加载。
  2. CSS 不会阻塞后续 DOM 结构的解析,不会阻塞其它资源(如图片)的加载,可是会阻塞 JS 文件的加载。
  3. 现代浏览器很聪明,会进行 prefetch 优化,浏览器在得到 html 文档以后会对页面上引用的资源进行提早下载。(注意仅仅只是提早下载)

这是关于”阻塞“我精读的第一篇文章,当初吸引个人一是归纳得很通俗易懂(如上),二是提供了一种实验方式,这种方式很便于经过实验增强对阻塞的理解,更是我第一次写Node.js。dom

下图是实验的运行方式,前提是已经装了node。


文章中的实验很简单,分析的也颇有道理的样子,我觉得这一篇就能帮我搞定对”阻塞“的理解。然而,问题来了,看着实验结果,有点懵,感受”阻塞“更难懂了。下图为文章demo的实验结果:


看了实验结果,感受跟文章中的结论对不上啊,下面是我产生的一系列疑问:

  1. 彷佛css和js文件的加载都阻塞了后续dom解析和文件的加载(不论是css仍是js文件),好比red.css和green.css加载时明显阻塞了DOM解析现象,后续dom(third line、fourth line)并无解析。
  2. 背景色发生屡次变化,也就是说过程当中发生了屡次渲染,dom还没解析完毕,怎么就渲染了呢?

二、实验2——搜集资料并分析问题

带着疑惑我将index.html中的代码作了以下修改,主要是按咱们更常见的方式将<link><script>标签放在<head>标签里:

<head>    
    <script src="/yellow.js"></script>    <!--5s-->    
    <link rel="stylesheet" href="/red.css">    <!--15s-->   
    <script src="/blue.js"></script>    <!--10s-->   
    <link rel="stylesheet" href="/green.css">    <!--20s-->
</head>
<body>    
    <p>First Line</p>    
    <p>Second Line</p>   
    <p>Third Line</p>    
    <p>Fourth Line</p>    
    <p>Fifth Line</p>
</body>复制代码

js文件里的内容都是改变颜色,如blue.js:

document.body.style.cssText = "background: blue !important";复制代码

实验现象:


  1. 等待yellow.js加载,阻塞DOM解析
  2. red.css不阻塞后续解析,所以继续解析到blue.js
  3. blue.js阻塞
  4. green.css不阻塞DOM解析,完成<body>里内容的解析
  5. 等待green.css加载完成后渲染最终页面。
这里说明下最终页面渲染成绿色的缘由,原本js定义的样式是内联样式,优先级高于CSS文件中定义的样式,但由于js执行时还没有解析到body,document.body=null,没法对其进行样式设置。能够参考实验1一块儿理解。


实验分析

结合实验2的实验现象,和期间对相关资料的阅读学习,对实验1所在文章中的一些说法和实验现象说说个人理解。

  1. “JS 会阻塞后续 DOM 解析以及其它资源(如 CSS,JS 或图片资源)的加载”——出自实验1原文。
    其实js阻塞的就只是DOM的解析,缘由不少文章都有提,这里不赘述,个人理解就是为了不冲突。
    而“阻塞其余资源的加载”一说则不够准确,实际上就只是DOM解析终止,没有将相关标签好比<link>挂在DOM树上,但这些资源的下载则一开始就开始了且与DOM解析并行。
  2. 实验1发生屡次渲染现象,而实验2只在DOM解析结束且CSS加载完毕后进行一次渲染,这是为何呢?由于<body>里 <link>,<script>标签都会触发一次渲染,都会阻塞dom的解析。
  3. 实验2说明了js阻塞了DOM解析,CSS不阻塞DOM解析,但阻塞页面渲染。那CSS阻塞js的执行吗?请看实验3。

三、实验3——CSS阻塞JS运行

由于以前的js代码都是经过link标签引入的,这里实验3就直接将执行代码写进<script>标签里,但实际上,结果是同样的。不少事情,只要明白原理,那么就会一通百通啦~

<head>
    <link rel="stylesheet" href="/red.css"><!-- wait="15s"-->    
    <script>        
        console.log('hello')    
    </script>
</head>复制代码


经过实验3能够看出CSS阻塞了js的执行,那么这是为何呢?

个人拙见,其实和JS阻塞DOM解析是出于相同的考虑——为了不冲突。JS阻塞DOM解析的缘由相信各位都知道,而JS代码不只能够操做DOM节点,也能够操做CSSOM上的节点,所以也应该不能同时进行,要等到CSS加载完成。一样的,JS执行也会阻止CSSOM的解析。可能有人会有点绕不明白,DOM解析和JS互斥执行时是JS阻塞了DOM解析,为何CSSOM这里确是CSS阻塞了JS?个人理解HTML解析是至上而下的,其生效顺序也是至上而下、后者覆盖前者,而不是下载完成顺序,所以要等被阻塞JS的前面的CSS解析完才能执行,后面的就没必要管了。

四、小结

1)<head>里

  •  js阻塞DOM解析
  • css不阻塞DOM解析,但阻塞页面渲染和js的执行
2)<body>里<link> <script>会触发页面渲染, 于是若是前面CSS资源(head里)还没有加载完成时,浏览器会等待它加载完毕再执行脚本

3)其余(一些学来的知识点,只有结论,没作实验):

  • 图像是不会阻塞页面的首次渲染
  •  资源的下载一开始就开始 
  • 浏览器在遇到<body>标签以前不会渲染页面的任何部分

关键渲染路径

关键渲染路径及其优化是个比较复杂的问题,感兴趣的朋友们能够点这里

  • 关键资源:可能阻塞网页首次渲染的资源;
  • 关键路径长度:获取全部关键资源所需的往返次数或总时间;
  • 关键字节:实现网页首次渲染所需的总字节数,是全部关键资源传送文件大小的总和。

举个简单的HTML+1个JS文件+1个CSS文件的例子以下:


上图关键路径特性为:

  • 3项关键资源
  • 2次或更屡次往返的最短关键路径长度
  • 11KB的关键字节
想要尽早进行页面首次渲染,提高用户体验,实际上就是对关键资源、关键路径、关键字节的优化。

优化建议

研究了这么多,固然是但愿可以加快首屏加载速度,这里从减小阻塞和优化关键三要素的角度提出一些优化建议。

一、CSS的优化建议

  • 将CSS置于文档head标签内
  • 【优化关键资源】经过CSS“媒体类型”和“媒体查询”来标记某些CSS为非阻塞资源。e.g.
    <link href="print.css" rel="stylesheet" media="print">
    <link href="other.css" rel="stylesheet" media="(min-width: 40em)">复制代码
  • 【优化关键路径】避免使用CSS import 
  • 【优化关键字节】Minify CSS文件 

 二、JavaScript的优化建议

  • 【优化阻塞】把脚本放在页面尾部 </body> 以前的位置
  • 【优化阻塞】使用async或defer指令来避免阻塞渲染 
  • 【优化阻塞】避免运行时间长的JavaScript,可考虑拆分,以便浏览器间隔处理其余事件
  • 【优化关键路径】减小JavaScript文件嵌套调用
  • 【优化关键字节】Minify JavaScript文件

三、async和defer

  • async:下载完成后执行,执行顺序乱序。可是当下载完成的时刻,渲染又会阻塞了,这是由于脚本执行了,当脚本执行完毕,渲染恢复。
  • defer:全部元素解析完成后,DOMContentLoaded前,按加载顺序执行,支持内联脚本。
  • DOMContentLoaded 是指在 dom 树构建完毕以后触发的事件, 而 onload 是 dom 树构建以及所有依赖资源(含图片)都下载完毕以后触发。

四、''link'预加载

功能很强大的样子,推荐你们看这里,已经写得很详细了。

参考文献

《<link>预加载功能详解》

《渲染树构建、布局及绘制》

《为何说DOM操做很慢》

《JS和CSS的位置对资源加载顺序的影响》

《CSS加载会形成阻塞吗?》

《原来CSS与JS是这样阻塞DOM解析和渲染的》

《一个微信面试题引起的血案——[译]什么阻塞了DOM》

相关文章
相关标签/搜索