浏览器渲染机制

1、引入

做为一名前端开发人员,和本身打交道最多的就是浏览器了,那么当浏览器拿到咱们的写的代码后,是如何工做的呢?这个过程,就是浏览器的渲染机制,今天咱们来好好分析一下。css

2、浏览器组成部分

一个浏览器都包含那些元素部分呢?html

  • 用户界面 - 包括地址栏、前进/后退按钮、书签菜单等。除了浏览器主窗口显示的你请求的页面外,其余显示的各个部分都属于用户界面。
  • 渲染引擎 -解析DOM文档和CSS规则并将内容排版到浏览器中显示有样式的界面,也有人称之为排版引擎,咱们常说的浏览器内核主要指的就是渲染引擎
  • JS解释器-用来解释执行JS脚本的模块,如 V8 引擎、JavaScriptCore
  • 网络 - 用于网络调用,好比 HTTP 请求。其接口与平台无关,并为全部平台提供底层实现。
  • UI 后端 -用来绘制基本的浏览器窗口内控件,如输入框、按钮、单选按钮等,根据浏览器不一样绘制的视觉效果也不一样,但功能都是同样的。
  • 数据存储 这是持久层。浏览器须要在硬盘上保存各类数据,例如 Cookie。新的 HTML 规范 (HTML5)定义了“网络数据库”,这是一个完整(可是轻便)的浏览器内数据库。
  • 浏览器引擎 - 在用户界面和渲染引擎之间传送指令。

3、浏览器内核

浏览器中最重要的部分就是浏览器内核,也就是渲染引擎,这个引擎是浏览器的核心,它的开发难度极大,内核代码大概有2400万行代码,可见其复杂程度极大。前端

1.浏览器渲染引擎承担什么任务呢?

渲染引擎,负责对网页语法的解释(如HTML、JavaScript)并渲染网页。渲染引擎决定了浏览器如何显示网页的内容以及页面的格式信息。webpack

ps:不一样的浏览器内核对网页编写语法的解释也有不一样,所以同一网页在不一样的内核的浏览器里的渲染(显示)效果也可能不一样,这也是网页编写者须要在不一样内核的浏览器中测试网页显示效果的缘由。web

2.具体渲染的流程是什么?

归纳:浏览器的渲染概念就是html解析成DOM,css造成样式规则。二者共同构建渲染树。浏览器根据渲染树的样式进行布局和渲染。算法

  1. 当浏览器拿到HTML文档时首先会进行HTML文档解析,构建DOM树。数据库

  2. 遇到css样式如link标签或者style标签时开始解析css,构建样式树。HTML解析构建和CSS的解析是相互独立的并不会形成冲突,所以咱们一般将css样式放在head中,让浏览器尽早解析css。后端

  3. 当html的解析遇到script标签会怎样呢?答案是中止DOM树的解析开始下载js。由于js是会阻塞html解析的,是阻塞资源。其缘由在于js可能会改变html现有结构。例若有的节点是用js动态构建的,在这种状况下就会中止dom树的构建开始下载解析js。浏览器

    ​ 脚本在文档的何处插入,就在何处执行。当 HTML 解析器遇到一个 script 标记时,它会暂停构建 DOM,将控制权移交给 JavaScript 引擎;等 JavaScript 引擎运行完毕,浏览器会从中断的地方恢复 DOM 构建。而所以就会推迟页面首绘的时间。能够在首绘不须要js的状况下用async和defer实现异步加载。这样js就不会阻塞html的解析了。当HTML解析完成后,浏览器会将文档标注为交互状态,并开始解析那些处于“deferred”模式的脚本,也就是那些应在文档解析完成后才执行的脚本。而后,文档状态将设置为“完成”,一个“加载”事件将随之触发。注意,异步执行是指下载。执行js时仍然会阻塞。网络

  4. 在获得DOM树和样式树后就能够进行渲染树的构建了。应注意的是渲染树和 DOM 元素相对应的,但并不是一一对应。好比非可视化的 DOM 元素不会插入呈现树中,例如“head”元素。若是元素的 display 属性值为“none”,那么也不会显示在呈现树中(可是 visibility 属性值为“hidden”的元素仍会显示)

3. 渲染详解

  1. HTML解析分为标记化和树构建阶段

    • 标记化:这个的意思就是实现标签的提取。后台给前端传回的信息是一个一个的字符流。标记化就是经过识别先后尖括号,来区别哪一部分是标签,哪一部分是内容
    • 树构建:将提取的标签,添加到树结构中。同时这些元素还会存储在堆栈中,用于查找和纠正嵌套错误。在此阶段完成后,浏览器会将文档标注为交互状态。随即绑定的事件能够被触发了

    这里的交互状态是指,当浏览器已经开始执行js代码后,即便js代码尚未执行完,这时已经绑定的事件是能够被触发的。

  2. CSS解析

    • css是逆向解析,也就是说咱们应该

    • 在 Webkit 中没有规则树,所以会对匹配的声明遍历 4 次。首先应用非重要高优先级的属性(因为做为其余属性的依据而应首先应用的属性,例如 display),接着是高优先级重要规则,而后是普通优先级非重要规则,最后是普通优先级重要规则。这意味着屡次出现的属性会根据正确的层叠顺序进行解析。最后出现的最终生效。

  3. 渲染树

    • 样式树和DOM树链接在一块儿造成一个渲染树,渲染树用来计算可见元素的布局而且做为将像素渲染到屏幕上的过程的输入。
    • 布局使用流模型的Layout算法。所谓流模型,便是指Layout的过程只需进行一遍便可完成,后出如今流中的元素不会影响前出如今流中的元素,Layout过程只需从左至右从上至下一遍完成便可。
    • 但实际实现中,流模型会有例外。Layout是一个递归的过程,每一个节点都负责本身及其子节点的Layout。Layout结果是相对父节点的坐标和尺寸。其过程能够简述为:
      • 父节点肯定本身的宽度
      • 父节点完成子节点放置,肯定其相对坐标
      • 节点肯定本身的宽度和高度
      • 父节点根据全部的子节点高度计算本身的高度
    • 此时renderTree已经构建完毕,不过浏览器渲染树引擎并不直接使用渲染树进行绘制,为了方便处理定位(裁剪),溢出滚动(页内滚动),CSS转换/不透明/动画/滤镜,蒙版或反射,Z (Z排序)等,浏览器须要生成另一棵树 - 层树。所以绘制过程以下:
      • 获取 DOM 并将其分割为多个层(RenderLayer)
      • 将每一个层栅格化,并独立的绘制进位图中
      • 将这些位图做为纹理上传至 GPU
      • 复合多个层来生成最终的屏幕图像(终极layer)。

4、浏览器显示优化

1.相关定义

  • Repaint(重绘)——屏幕的一部分要重画,好比某个CSS的背景色变了。可是元素的几何尺寸没有变。
  • Reflow(重排)——意味着元件的几何尺寸变了,咱们须要从新验证并计算Render Tree。是Render Tree的一部分或所有发生了变化。这就是Reflow,或是Layout。reflow 会从这个root frame开始递归往下,依次计算全部的结点几何尺寸和位置。
  • 首屏时间——当浏览器显示第一屏页面所消耗的时间,在国内的网络条件下,一般一个网站,若是“首屏时间”在2秒之内是比较优秀的,5秒之内用户能够接受,10秒以上就不可容忍了。
    白屏时间
  • 白屏时间——指浏览器开始显示内容的时间。可是在传统的采集方式里,是在HTML的头部标签结尾里记录时间戳,来计算白屏时间。在这个时刻,浏览器开始解析身体标签内的内容。而现代浏览器不会等待CSS树(全部CSS文件下载和解析完成)和DOM树(整个身体标签解析完成)构建完成才开始绘制,而是立刻开始显示中间结果。因此常常在低网速的环境中,观察到页面由上至下缓慢显示完,或者先显示文本内容后再重绘成带有格式的页面内容。

2.优化办法

原理:了解浏览器如何进行解析,咱们能够在构建DOM结构,组织css选择器时,选择最优的写法,提升浏览器的解析速率。理解浏览器如何进行渲染,明白渲染的过程,咱们在设置元素属性,编写js文件时,能够减小”重绘“”从新布局“的消耗。

  • 一、减小资源请求的次数和压缩数据内容。 由于资源的请求是一个复杂的过程。网速相同的条件下,下载一个100KB的图片比下载两个50KB的图片要快。因此,请减小HTTP请求。
    ①进行资源打包,将须要屡次请求的资源进行打包减小资源请求次数,如webpack等。
    ②使用雪碧图,能够避免因不一样图片引发的屡次资源下载

  • 二、高效合理的css选择符能够减轻浏览器的解析负担。

    由于css是逆向解析的因此应当避免多层嵌套。
    避免使用通配规则。如 *{} 计算次数惊人!只对须要用到的元素进行选择
    不要去用标签限定ID或者类选择符。如:ul#nav,应该简化为#nav
    尽可能少的去使用后代选择器,下降选择器的权重值。后代选择器的开销是最高的,尽可能将选择器的深度降到最低,最高不要超过三层,更多的使用类来关联每个标签元素。
    考虑继承。了解哪些属性是能够经过继承而来的,而后避免对这些属性重复指定规则

  • 三、从js层面谈页面优化
    ①解决渲染阻塞
    若是在解析HTML标记时,浏览器遇到了JavaScript,解析会中止。只有在该脚本执行完毕后,HTML渲染才会继续进行。因此这阻塞了页面的渲染。
    解决方法:在标签中使用 async或defer特性
    ②减小对DOM的操做
    对DOM操做的代价是高昂的,这在网页应用中的一般是一个性能瓶颈。
    解决办法:修改和访问DOM元素会形成页面的Repaint和Reflow,循环对DOM操做更是罪恶的行为。因此请合理的使用JavaScript变量储存内容,考虑大量DOM元素中循环的性能开销,在循环结束时一次性写入。
    减小对DOM元素的查询和修改,查询时可将其赋值给局部变量。
    ③使用JSON格式来进行数据交换
    JSON是一种轻量级的数据交换格式,采用彻底独立于语言的文本格式,是理想的数据交换格式。同时,JSON是 JavaScript原生格式,这意味着在 JavaScript 中处理 JSON数据不须要任何特殊的 API 或工具包。
    ④让须要常常改动的节点脱离文档流 由于重绘有时确实不可避免,因此只能尽量限制重绘的影响范围。