前言

浏览器的内核是指反对浏览器运行的最外围的程序,分为两个局部的,一是渲染引擎,另一个是JS引擎。渲染引擎在不同的浏览器中也不是都雷同的。目前市面上常见的浏览器内核能够分为这四种:Trident(IE)、Gecko(火狐)、Blink(Chrome、Opera)、Webkit(Safari)。这外面大家最耳熟能详的可能就是 Webkit 内核了,Webkit 内核是当下浏览器世界真正的霸主。
本文咱们就以 Webkit 为例,对古代浏览器的渲染过程进行一个深度的分析。

页面加载过程

在介绍浏览器渲染过程之前,咱们简明扼要介绍下页面的加载过程,有助于更好了解后续渲染过程。

要点如下:

  • 浏览器依据 DNS 服务器失去域名的 IP 地址
  • 向这个 IP 的机器发送 HTTP 申请
  • 服务器收到、解决并返回 HTTP 申请
  • 浏览器失去返回内容
https://juejin.im/timelinejuejin.im36.248.217.149

服务端接管到 HTTP 申请,而后通过计算(向不同的用户推送不同的内容),返回 HTTP 申请,返回的内容如下:

其实就是一堆 HMTL 格局的字符串,因为只有 HTML 格局浏览器能力正确解析,这是 W3C 规范的要求。接下来就是浏览器的渲染过程。

浏览器渲染过程

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

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过程。

3)最初通过调用操作系统Native GUI的API绘制。

接下来咱们针对这其中所经验的重要步骤具体论述

构建DOM

浏览器会恪守一套步骤将HTML 文件转换为 DOM 树。宏观上,能够分为几个步骤:

  • 浏览器从磁盘或网络读取HTML的原始字节,并依据文件的指定编码(例如 UTF-8)将它们转换成字符串。

在网络中传输的内容其实都是 0 和 1 这些字节数据。当浏览器接管到这些字节数据当前,它会将这些字节数据转换为字符串,也就是咱们写的代码。

这时候你肯定会有疑难,节点与节点之间的关系如何保护?

事实上,这就是Token要标识“起始标签”和“完结标签”等标识的作用。例如“title”Token的起始标签和完结标签之间的节点必定是属于“head”的子节点。

上图给出了节点之间的关系,例如:“Hello”Token位于“title”开始标签与“title”完结标签之间,表明“Hello”Token是“title”Token的子节点。同理“title”Token是“head”Token的子节点。

  • 生成节点对象并构建DOM

事实上,构建DOM的过程中,不是等所有Token都转换实现后再去生成节点对象,而是一边生成Token一边耗费Token来生成节点对象。换句话说,每个Token被生成后,会立即耗费这个Token创立出节点对象。留神:带有完结标签标识的Token不会创立节点对象。

接下来咱们举个例子,假如有段HTML文本:

<html>
<head>
    <title>Web page parsing</title>
</head>
<body>
    <div>
        <h1>Web page parsing</h1>
        <p>This is an example Web page.</p>
    </div>
</body>
</html>

下面这段HTML会解析成这样:

构建CSSOM

DOM会捕捉页面的内容,但浏览器还须要晓得页面如何展现,所以须要构建CSSOM。

构建CSSOM的过程与构建DOM的过程十分类似,当浏览器接管到一段CSS,浏览器首先要做的是辨认出Token,而后构建节点并生成CSSOM。

在这一过程中,浏览器会确定下每一个节点的款式到底是什么,并且这一过程其实是很耗费资源的。因为款式你能够自行设置给某个节点,也能够通过继承取得。在这一过程中,浏览器得递归 CSSOM 树,而后确定具体的元素到底是什么款式。

留神:CSS匹配HTML元素是一个相当简单和有性能问题的事件。所以,DOM树要小,CSS尽量用id和class,千万不要过渡层叠上来

构建渲染树

当咱们生成 DOM 树和 CSSOM 树当前,就须要将这两棵树组合为渲染树。

display: none

咱们或者有个纳闷:浏览器如果渲染过程中遇到JS文件怎么解决

也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是都倡议将 script 标签放在 body 标签底部的起因。当然在当下,并不是说 script 标签必须放在底部,因为你能够给 script 标签增加 defer 或者 async 属性(下文会介绍这两者的区别)。

JS文件不只是阻塞DOM的构建,它会导致CSSOM也阻塞DOM的构建

本来DOM和CSSOM的构建是互不影响,井水不犯河水,然而一旦引入了JavaScript,CSSOM也开始阻塞DOM的构建,只有CSSOM构建结束后,DOM再复原DOM构建。

这是什么状况?

这是因为JavaScript不只是能够改DOM,它还能够更改款式,也就是它能够更改CSSOM。因为不残缺的CSSOM是无奈应用的,如果JavaScript想拜访CSSOM并更改它,那么在执行JavaScript时,必须要能拿到残缺的CSSOM。所以就导致了一个景象,如果浏览器尚未实现CSSOM的下载和构建,而咱们却想在此时运行脚本,那么浏览器将提早脚本执行和DOM构建,直至其实现CSSOM的下载和构建。也就是说,在这种状况下,浏览器会先下载和构建CSSOM,而后再执行JavaScript,最初在持续构建DOM

布局与绘制

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

布局流程的输入是一个“盒模型”,它会准确地捕捉每个元素在视口内的确切地位和尺寸,所有绝对测量值都将转换为屏幕上的相对像素。

布局实现后,浏览器会立刻收回“Paint Setup”和“Paint”事件,将渲染树转换成屏幕上的像素。

以上咱们具体介绍了浏览器工作流程中的重要步骤,接下来咱们探讨几个相干的问题:

几点补充阐明

1.async和defer的作用是什么?有什么区别?

接下来咱们比照下 defer 和 async 属性的区别:

其中蓝色线代表JavaScript加载;红色线代表JavaScript执行;绿色线代表 HTML 解析。

没有 defer 或 async,浏览器会立刻加载并执行指定的脚本,也就是说不期待后续载入的文档元素,读到就加载并执行。

async 属性示意异步执行引入的 JavaScript,与 defer 的区别在于,如果曾经加载好,就会开始执行——无论此刻是 HTML 解析阶段还是 DOMContentLoaded 触发之后。须要留神的是,这种形式加载的 JavaScript 仍然会阻塞 load 事件。换句话说,async-script 可能在 DOMContentLoaded 触发之前或之后执行,但肯定在 load 触发之前执行。

defer 属性示意提早执行引入的 JavaScript,即这段 JavaScript 加载时 HTML 并未进行解析,这两个过程是并行的。整个 document 解析结束且 defer-script 也加载实现之后(这两件事件的程序无关),会执行所有由 defer-script 加载的 JavaScript 代码,而后触发 DOMContentLoaded 事件。

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

2.为什么操作 DOM 慢

把 DOM 和 JavaScript 各自设想成一个岛屿,它们之间用免费桥梁连贯。——《高性能 JavaScript》

JS 是很快的,在 JS 中批改 DOM 对象也是很快的。在JS的世界里,所有是简略的、迅速的。但 DOM 操作并非 JS 一个人的独舞,而是两个模块之间的合作。

因为 DOM 是属于渲染引擎中的货色,而 JS 又是 JS 引擎中的货色。当咱们用 JS 去操作 DOM 时,实质上是 JS 引擎和渲染引擎之间进行了“跨界交换”。这个“跨界交换”的实现并不简略,它依赖了桥接接口作为“桥梁”(如下图)。

过“桥”要免费——这个开销自身就是不可疏忽的。咱们每操作一次 DOM(不论是为了批改还是仅仅为了拜访其值),都要过一次“桥”。过“桥”的次数一多,就会产生比拟显著的性能问题。因而“缩小 DOM 操作”的倡议,并非空穴来风。

3.你真的理解回流和重绘吗

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

留神:上图流程中有很多连接线,这示意了Javascript动静批改了DOM属性或是CSS属性会导致从新Layout,但有些扭转不会从新Layout,就是上图中那些指到天上的箭头,比方批改后的CSS rule没有被匹配到元素。

这里重要要说两个概念,一个是Reflow,另一个是Repaint

  • 重绘:当咱们对 DOM 的批改导致了款式的变动、却并未影响其几何属性(比方批改了色彩或背景色)时,浏览器不需从新计算元素的几何属性、间接为该元素绘制新的款式(跳过了上图所示的回流环节)。
  • 回流:当咱们对 DOM 的批改引发了 DOM 几何尺寸的变动(比方批改元素的宽、高或暗藏元素等)时,浏览器须要从新计算元素的几何属性(其余元素的几何属性和地位也会因而受到影响),而后再将计算的后果绘制进去。这个过程就是回流(也叫重排)

咱们晓得,当网页生成的时候,至多会渲染一次。在用户拜访的过程中,还会一直从新渲染。从新渲染会反复回流+重绘或者只有重绘。
回流必定会产生重绘,重绘不肯定会引发回流。重绘和回流会在咱们设置节点款式时频繁呈现,同时也会很大水平上影响性能。回流所需的老本比重绘高的多,扭转父节点里的子节点很可能会导致父节点的一系列回流。

1)常见引起回流属性和办法

任何会扭转元素几何信息(元素的地位和尺寸大小)的操作,都会触发回流,

  • 增加或者删除可见的DOM元素;
  • 元素尺寸扭转——边距、填充、边框、宽度和高度
  • 内容变动,比方用户在input框中输出文字
  • 浏览器窗口尺寸扭转——resize事件产生时
  • 计算 offsetWidth 和 offsetHeight 属性
  • 设置 style 属性的值

2)常见引起重绘属性和办法

3)如何缩小回流、重绘

  • 应用 transform 代替 top
  • 应用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(扭转了布局)
  • 不要把节点的属性值放在一个循环里当成循环里的变量。
for(let i = 0; i < 1000; i++) {
    // 获取 offsetTop 会导致回流,因为须要去获取正确的值
    console.log(document.querySelector('.test').style.offsetTop)
}
  • 不要应用 table 布局,可能很小的一个小改变会造成整个 table 的从新布局
  • 动画实现的速度的抉择,动画速度越快,回流次数越多,也能够抉择应用 requestAnimationFrame
  • CSS 选择符从右往左匹配查找,防止节点层级过多
  • 将频繁重绘或者回流的节点设置为图层,图层可能阻止该节点的渲染行为影响别的节点。比方对于 video 标签来说,浏览器会主动将该节点变为图层。

性能优化策略

基于下面介绍的浏览器渲染原理,DOM 和 CSSOM 构造构建程序,初始化能够对页面渲染做些优化,晋升页面性能。

总结

综上所述,咱们得出这样的论断:

  • 浏览器工作流程:构建DOM -> 构建CSSOM -> 构建渲染树 -> 布局 -> 绘制。
  • CSSOM会阻塞渲染,只有当CSSOM构建结束后才会进入下一个阶段构建渲染树。
  • 通常状况下DOM和CSSOM是并行构建的,然而当浏览器遇到一个不带defer或async属性的script标签时,DOM构建将暂停,如果此时又凑巧浏览器尚未实现CSSOM的下载和构建,因为JavaScript能够批改CSSOM,所以须要等CSSOM构建结束后再执行JS,最初才从新DOM构建。