前端渲染机制

引言

       浏览器的内核是指支持浏览器运行的最核心的程序,分为两个部分的,一是渲染引擎,另一个是JS引擎。不同的浏览器有不同的内核,Trident(IE)、Gecko(火狐)、Blink(Chrome、Opera)、Webkit(Safari)。这里面大家最耳熟能详的可能就是 Webkit 内核了,Webkit 内核是当下浏览器世界真正的霸主。

页面加载过程

浏览器加载网页时,要经过以下几个要点:

  • 浏览器根据 DNS 服务器得到域名的 IP 地址
  • 向这个IP的服务器发送HTTP请求
  • 服务器收到后 处理并返回HTTP请求
  • 浏览器得到返回的内容

例如:在浏览器输入 https://juejin.im/timeline,然后经过 DNS 解析, juejin.im对应的 IP 是 36.248.217.149(不同时间、地点对应的 IP 可能会不同)。然后浏览器向该 IP 发送 HTTP 请求。服务端接收到 HTTP 请求,然后经过计算(向不同的用户推送不同的内容),返回 HTTP 请求,其实就是一堆 HMTL 格式的字符串,因为只有 HTML 格式浏览器才能正确解析,这是 W3C 标准的要求。

浏览器渲染过程

image
浏览器渲染过程大体分为如下三部分:

1. 浏览器会解析三个东西:

  • 一是HTML/SVG/XHTML,HTML字符串描述了一个页面的结构,浏览器会把HTML结构字符串解析转换DOM树形结构。
  • 二是CSS,解析CSS会产生CSS规则树,它和DOM结构比较像。
  • 三是Javascript脚本,等到Javascript 脚本文件加载后, 通过 DOM API 和 CSSOM API 来操作 DOM Tree 和 CSS Rule Tree。

2. 解析完成后,浏览器引擎会通过DOM Tree 和 CSS Rule Tree 来构造 Rendering Tree。

  • Rendering Tree 渲染树并不等同于DOM树,渲染树只会包括需要显示的节点和这些节点的样式信息。
  • CSS 的 Rule Tree主要是为了完成匹配并把CSS Rule附加上Rendering Tree上的每个Element(也就是每个Frame)。
  • 计算每个Frame 的位置,这又叫layout和reflow过程。
    最后通过调用操作系统Native GUI的API绘制。

    构建DOM

    浏览器会遵守一套步骤将HTML 文件转换为 DOM 树。宏观上,可以分为几个步骤:
    image
  • 浏览器从磁盘或网络读取HTML的原始字节,并根据文件的指定编码(例如 UTF-8)将它们转换成字符串。
  • 将字符串转换成Token,例如: 、 等。Token中会标识出当前Token是“开始标签”或是“结束标签”亦或是“文本”等信息
    image
  • 生成节点对象并构建DOM

    构建DOM的过程中,不是等所有Token都转换完成后再去生成节点对象,而是一边生成Token一边消耗Token来生成节点对象。换句话说,每个Token被生成后,会立刻消耗这个Token创建出节点对象。注意:带有结束标签标识的Token不会创建节点对象。

构建CSSOM

      构建CSSOM的过程与构建DOM的过程非常相似,当浏览器接收到一段CSS,浏览器首先要做的是识别出Token,然后构建节点并生成CSSOM。CSS匹配HTML元素是一个相当复杂和有性能问题的事情。所以,DOM树要小,CSS尽量用id和class,千万不要过渡层叠下去。

构建渲染树

      当浏览器构建完 DOM 树和 CSSOM 树以后,就需要将这两棵树组合为渲染树。
image
渲染树只会包括需要显示的节点和这些节点的样式信息,如果某个节点是 display:none 的,那么就不会在渲染树中显示。渲染过程中,如果遇到script标签就停止渲染,执行 JS 代码。因为浏览器渲染和 JS 执行共用一个线程,而且这里必须是单线程操作。JS文件不只是阻塞DOM的构建,它会导致CSSOM也阻塞DOM的构建。,浏览器会先下载和构建CSSOM,然后再执行JavaScript,最后在继续构建DOM。
因为,JS还可以更改样式,所以必须等到完整的CSSOM渲染完成

布局与绘制

      当浏览器生成渲染树以后,就会根据渲染树来进行布局(也可以叫做回流)。这一阶段浏览器要做的事情是要弄清楚各个节点在页面中的确切位置和大小。通常这一行为也被称为“自动重排”。

      布局流程的输出是一个“盒模型”,它会精确地捕获每个元素在视口内的确切位置和尺寸,所有相对测量值都将转换为屏幕上的绝对像素。布局完成后,浏览器会立即发出“Paint Setup”和“Paint”事件,将渲染树转换成屏幕上的像素。

补充

  1. async和defer的作用是什么?有什么区别?
  • 情况1 <scriptsrc="script.js"></script>
          没有 defer 或 async,浏览器会立即加载并执行指定的脚本,也就是说不等待后续载入的文档元素,读到就加载并执行。
  • 情况2 <script async src="script.js"></script> (异步下载)
          async 属性表示异步执行引入的 JavaScript,与 defer 的区别在于,如果已经加载好,就会开始执行——无论此刻是 HTML 解析阶段还是 DOMContentLoaded 触发之后。async-script 可能在 DOMContentLoaded 触发之前或之后执行,但一定在 load 触发之前执行
  • 情况3 <script defer src="script.js"></script>(延迟执行)
          defer 属性表示延迟执行引入的 JavaScript,即这段 JavaScript 加载时 HTML 并未停止解析,这两个过程是并行的。

      defer 与相比普通 script,有两点区别:载入 JavaScript 文件时不阻塞 HTML 的解析,执行阶段被放到 HTML 标签解析完成之后;在加载多个JS脚本的时候,async是无顺序的加载,而defer是有顺序的加载。

  1. 为什么操作 DOM 慢

    把 DOM 和 JavaScript 各自想象成一个岛屿,它们之间用收费桥梁连接。——《高性能 JavaScript》
    DOM 是属于渲染引擎中的东西,而 JS 又是 JS 引擎中的东西。当我们用 JS 去操作 DOM 时,本质上是 JS 引擎和渲染引擎之间进行了“跨界交流”,即渲染引擎和JS引擎之间的协作。因此,我们要尽量减少dom操作

iamge

  1. 你真的了解回流和重绘吗

    渲染的流程基本上是这样(如下图黄色的四个步骤):1.计算CSS样式 2.构建Render Tree 3.Layout – 定位坐标和大小 4.正式开画

Reflow和Repaint

  • 重绘: DOM 的样式修改(修改了颜色或背景色),浏览器不会重新计算元素的几何属性而重新布局,而是直接为该元素绘制新的样式
  • 回流:对 DOM 的修改引发了 DOM 几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性(其他元素的几何属性和位置也会因此受到影响),然后再将计算的结果绘制出来。这个过程就是回流(也叫重排)
    回流必定会发生重绘,重绘不一定会引发回流

  • 常见引起回流属性和方法(改变元素的位置和尺寸大小)

    • 添加或者删除可见的DOM元素;
    • 元素尺寸改变——边距、填充、边框、宽度和高度
    • 内容变化,比如用户在input框中输入文字
    • 浏览器窗口尺寸改变——resize事件发生时
    • 计算 offsetWidth 和 offsetHeight 属性
    • 设置 style 属性的值
  • 如何减少回流、重绘

    • 使用 transform 替代 top
    • 使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局)
    • 不要把节点的属性值放在一个循环里当成循环里的变量。

      1
      2
      3
      4
      for(let i = 0; i< 1000 ; i++){
      //获取 offsetTop 会导致回流,因为需要去获取正确的值
      console.log(document.querySelector('.test').style.offsetTop)
      }
    • 不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局

    • 动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用 requestAnimationFrame
    • CSS 选择符从右往左匹配查找,避免节点层级过多
    • 将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点。比如对于 video 标签来说,浏览器会自动将该节点变为图层。

性能优化策略

       基于上面介绍的浏览器渲染原理,DOM 和 CSSOM 结构构建顺序,初始化可以对页面渲染做些优化,提升页面性能。

  • js优化<script>标签加上 defer属性 和 async属性 用于在不阻塞页面文档解析的前提下,控制脚本的下载和执行。 defer属性: 用于开启新的线程下载脚本文件,并使脚本在文档解析完成后执行。 async属性: HTML5新增属性,用于异步下载脚本文件,下载完毕立即解释执行代码。
  • css优化<link>标签的 rel属性 中的属性值设置为 preload 能够让你在你的HTML页面中可以指明哪些资源是在页面加载完成后即刻需要的,最优的配置加载顺序,提高渲染性能

总结

  1. 浏览器工作流程:构建DOM -> 构建CSSOM -> 构建渲染树 -> 布局 -> 绘制。
  2. CSSOM会阻塞渲染,只有当CSSOM构建完毕后才会进入下一个阶段构建渲染树。
  3. 通常情况下DOM和CSSOM是并行构建的,但是当浏览器遇到一个不带defer或async属性的script标签时,DOM构建将暂停,如果此时又恰巧浏览器尚未完成CSSOM的下载和构建,由于JavaScript可以修改CSSOM,所以需要等CSSOM构建完毕后再执行JS,最后才重新DOM构建。
-------------本文结束感谢您的阅读-------------