前端开发需要了解的浏览器架构及渲染原理

444 阅读9分钟

浏览器是我们前端开发小伙伴们在日常开发中最关心的工具之一,我们编写的代码最终目标都要在浏览器内渲染呈现给用户。了解浏览器架构及渲染流程,会更有助于我们的开发中以更合理的方式编写代码,有助于提高页面的加载及渲染速度。

本篇文章以Chrome浏览器为例说明。

1. 浏览器多进程架构

在了解浏览器多进程架构之前,我们需要了解知道两个概念:进程线程

  • 进程:指在系统中能独立运行并作为资源(内存)分配的基本单位,是一个能独立运行的活动实体。进程之间相互独立,进程之间可以通过IPC通信。
  • 线程:线程是进程中的一个实体,作为系统调度和分派的基本单位。一个进程中可以包含多个线程,多个线程共享所在进程的内存,线程并不拥有资源(内存)而是使用他们。

早期的浏览器采用单进程结构,即浏览器的所有功能模块(缓存数据、文件、网络、渲染、插件)都是运行在同一个进程里。

单进程浏览器的缺点:

  • 不稳定:因为所有功能模块都运行在一个进程里面,任何一个功能模块出现问题,都会引起浏览器进程崩溃。
  • 效率低:同一时间只有一个模块运行。

现代浏览器基本都采用多进程架构,如图

架构.jpeg

在Chrome浏览器中,我们可以通过点击右上角“选项”-“更多工具”-“任务管理器”查看

架构1.png

我们看到,最新的浏览器多进程架构包括:1个浏览器主进程、1个网络进程、1个GPU进程、多个渲染进程、多个插件进程。

  • 浏览器主进程:主要负责浏览器界面展示(地址栏、书签、前进后退)、用户交互、子进程管理,同时提供存储等功能。
  • GPU进程:主要负责3D绘制。
  • 网络进程:主要负责页面网络资源请求加载。
  • 渲染进程:负责脚本执行(html、js、css)、页面渲染、事件处理等。一般情况下,Chrome 会为每个 Tab 标签创建一个渲染进程。
  • 插件进程:一个插件一个进程,主要负责插件的运行。因为第三方插件更容易崩溃,所以提供插件进程来确保插件的奔溃不会影响浏览器其它进程的正常运行。

1. 1 多进程之间的协调

我们以从输入url到页面渲染这个老生常谈的话题来举例多进程之间的协调。

浏览器主进程处理用户输入 当用户在浏览器地址栏输入内容执行搜索之后,浏览器主进程中的UI线程会根据输入内容判断输入为url还是一个搜索项内容。如果输入内容为搜索项,则将采用浏览器默认搜索引擎执行内容搜索;如果输入内容为url,则将url提交给网络线程发起网络请求。

网络进程发起网络请求 在上一步浏览器主进程通过IPC把URL请求发送至网络进程,网络进程接受到URL请求后,发起真正的网络请求。

首先,网络进程会查找本地缓存是否有该资源,有则直接返回;没有就进入请求。经过:DNS解析 》 建立TCP连接 》 接受返回信息 》 解析响应。

在网络进程请求完成之后,会获取到对应服务器的返回数据。但是服务器返回的数据类型有很多种。网络进程会根据响应头Content-Type来判断返回值的类型,如果返回的是HTML,网络进程会把数据提交给渲染进程;如果是一个zip、excel、doc等文件,则代表是一次下载请求,网络进程会把数据发送给下载管理器。

渲染进程解析html渲染页面 不同源的界面将会有一个单独的渲染进程;如果是同源的界面A打开界面B,则界面B将复用A的渲染进程。

在网络进程发起网络请求的同时,浏览器主进程会主动启动一个渲染进程,这样在网络进程处理完网络请求之后,渲染进程已经处于等待状态,等待网络进程提交文档。

  • 在网络进程获取到服务器返回的数据之后,根据响应头信息Content-Type判断如果为html类型,浏览器主进程将通知渲染进程准备接受文档;
  • 此时网络进程和渲染进程通过IPC建立传输数据;
  • 文档数据传输完成之后,渲染进程会返回确认提交的消息给浏览器主进程;
  • 浏览器主进程在接收到确认提交的消息后,会更新浏览器tab界面状态、地址栏url、安全状态、前进后退历史等信息。

浏览器多进程协调流程图参考如下:

架构2.jpeg

2. 浏览器渲染原理

渲染进程的主要工作是将HTML,CSS和JavaScript转换为用户可以与之交互的网页。渲染流程参考如下图:

结构3.jpeg

2. 1 解析HTML生成DOM树

html文档的解析是一个从上到下,深度遍历的过程。DOM是一个可以被浏览器理解的结构并可以通过JS进行修改。

  • 解析过程中遇到同步的script标签,会暂停html的解析并发起网络请求加载js脚本并执行。
  • 解析过程中遇到异步的script标签,会并行进行html解析和发起网络请求加载js脚本。

整个解析过程的实际结束触发DOMContentLoaded事件。

html所有资源(包括音视频图片)加载请求加载完毕触发load事件。

谨记

  • 同步的script会阻塞html解析创建DOM。
  • 带有async的script也有可能阻塞html的解析创建DOM。
  • 带有defer的script不会阻塞html解析创建DOM。

现代浏览器总是并行加载资源,例如,当 HTML 解析器(HTML Parser)被脚本阻塞时,解析器虽然会停止构建 DOM,但仍会识别该脚本后面的资源,并进行预加载。

2. 2 创建CSSOM

在html文档解析构建DOM树的过程中,浏览器将会优先处理css资源,如果遇到link标签,则将发起网络请求加载css资源,css资源的加载并不会影响html解析dom树构建;

html文档中所有的link标签样式资源全部加载完毕与html文档style内部样式一起计算生成CSSOM;

CSSOM的构建会阻塞js引擎线程的执行,直至CSSOM构建完成。

谨记

  • css资源的加载不会阻塞DOM的构建
  • css会影响页面的渲染
  • CSSOM的创建过程会阻塞JS引擎线程执行。

2. 3 生成Render(渲染)树

在DOM树和CSSOM创建完成之后,通过合成DOM和CSSOM创建渲染树。DOM树和Render树并不是一一对应的,Render树是用来显示的,如header、display:none的这些元素并不会在Render树中出现。

2. 4 布局绘制

  • 布局:根据Render树计算出每个节点在浏览器屏幕的位置。
  • 绘制:通过显卡将布局后的节点内容呈现在屏幕。
3. 浏览器资源加载优先级

当浏览器开始解析网页,并开始下载图片、Script、 CSS 等资源的时候,浏览器会为每个资源分配一个代表资源下载优先级的 fetch priority 标志。了解资源优先级,就可以知道浏览器是按照什么顺序来加载这些资源。

在Chrome浏览器中,优先级被分为5个等级:

  • Highset 最高
  • High 高
  • Medium 中
  • Low 低
  • Lowest 最低

3. 1 默认优先级

  • html、css、字体这三种资源拥有最高优先级。
  • 其次是带有preload标识资源、script、xhr请求。
  • 图片、语音、视频。
  • prefetch拥有最低优先级

3. 2 默认优先级调整

  • 对于XHR请求,将同步的XHR请求调整为最高优先级。
  • 对于图片资源,图片默认拥有Low低优先级,现代浏览器为提高首屏用户体验,将位于视口内的图片提升为High高优先级。
  • 对于script脚本资源,带有async和defer标识的异步资源降低为Low低优先级;对于同步的script脚本,又根据是否在浏览器展示的第一张图片之前还是后面又分为两类,之前调整为High,之后调整为Medium中优先级。
  • preload 使用 “as” 属性加载的资源将会获得与资源 “type” 属性所拥有的相同的优先级。比如说,preload as="style" 将会获得比 as=“script” 更高的优先级
  • 不带 “as” 属性的 preload 的优先级将会等同于异步请求。

3. 3 preload和prefetch

preload和prefetch是link标签rel属性的可选属性值。

preload:提供了一种声明式的命令,让浏览器提前加载指定资源(加载后并不执行),需要执行时再执行。

preload 有 as 属性,浏览器可以设置正确的资源加载优先级,这种方式可以确保资源根据其重要性依次加载, 所以,preload既不会影响重要资源的加载,又不会让次要资源影响自身的加载;

如果忽略 as 属性,或者错误的 as 属性会使 preload 等同于 XHR 请求,浏览器不知道加载的是什么,因此会赋予此类资源非常低的加载优先级。

prefetch:它的作用是告诉浏览器加载下一页面可能会用到的资源,由于资源并不是当前页面使用,所以会被赋予最低优先级。

注意:不要混用preload和prefetch。混用并不会复用资源,二是重复请求。

<link rel="preload" href="a.css">
<link rel="prefetch" href="a.css">