浏览器页面渲染过程
浏览器的内部结构
如下图所示,8个子系统组合构成了浏览器。
-
用户界面:包括地址栏、后退/前进按钮、书签目录等 -
浏览器引擎:查询及操作渲染引擎的接口 -
渲染引擎:显示请求的内容,比如请求内容为 HTML,则负责解析 HTML 、CSS,并将解析后的结果显示出来 -
网络子系统:完成网络调用,比如 HTTP 请求,它具有平台无关的接口,可以在不同平台工作 -
Javascript 解释器:解释执行 Javascript 代码 -
XML 解析器:解析 XML -
显示后端:绘制类似组合选择框及对话框等基本组件,具有不特定于某个平台的通用接口,底层使用操作系统的用户接口 -
数据持久性子系统:属于持久层,浏览器需要在硬盘中保存类似 Cookie 的各种数据
其中页面加载和渲染,离不开浏览器引擎、渲染引擎、网络子系统、JavaScript 解释器
进程与线程
-
进程是操作系统资源分配的基本单位,进程中包含线程 -
线程是由进程所管理的,为了提升浏览器的稳定性和安全性,浏览器采用了多进程模型
前端开发平常工作离不开 Chrome 浏览器,现以其为例介绍进程和线程
Chrome 多进程和多线程架构
Chrome 浏览器主要包括4个进程
-
浏览器进程:处理选项卡之外的内容,用于控制用户可见的 UI 部分(比如地址栏,书签,后退、前进按钮)和用户不可见的隐藏部分(比如网格请求和文件访问),支持多线程
-
UI 线程:绘制浏览器的按钮和输入字段 -
网络线程:发送请求,接收数据 -
存储线程:控制对文件的访问 -
GPU 进程:处理图像,3d 绘制,提高性能
-
渲染进程:每个选项卡都有单独的渲染进程,核心用于渲染页面,支持多线程
-
GUI 渲染线程:渲染浏览器界面 -
JavaScript 引擎线程:解析执行 JavaScript;与 GUI 渲染线程互斥 -
浏览器定时触发线程:setTimeout、setInterval 相关的线程 -
浏览器事件触发线程:处理浏览器事件,事件触发后需执行的代码传递给 JavaScript 引擎线程执行 -
插件进程:管理 Chrome 中安装的插件
浏览器中页面渲染过程
页面导航过程
用户输入 URL,浏览器进程进行请求和准备处理,流程图(包含依赖渲染器进程的过程)如下:
页面渲染过程
获取到资源后,渲染器进程处理选项卡内容的渲染,渲染过程如下图:
渲染过程详解:
解析
-
在解析前,会执行预解析操作,会预先加载 CSS、JS 等文件 -
HTML 解析器解析 HTML,生成 DOM 树 -
CSS 解析器解析 CSS,产生 CSS 规则树 -
解析 JS 脚本,该过程中需要等待 JS 执行完成才继续解析 HTML
从 HTML 到 DOM
-
字节流解码
浏览器通过 HTTP 协议接收到的文档内容是字节数据,通过算法确定字符编码,根据字符编码将字节数据解码成字符数据(即开发编写的代码)
-
输入流预处理
将上一步得到的字符数据进行统一格式化,比如将换行符转换成统一格式
-
令牌化
将字符数据转化为令牌(Token),不同状态下接收同样的字符数据会产生不同的结果
-
构建 DOM 树
浏览器创建解析器的同时,会创建一个 Document 对象
在树构建阶段,Document 作为根节点,不断地被修改和扩充,树构建器接收到某个令牌后,创建该令牌对应的 DOM 元素,并将该元素插入到 DOM 树中
为纠正元素标签嵌套错位,以及处理未关闭的元素标签,树创建器创建的新 DOM 元素还会被插入到一个开放元素栈中
从 CSS 到 CSSOM
渲染引擎解析 CSS 过程与解析 HTML 步骤一致,都会生成树状结构
不同点在于,CSS 在被转换成浏览器能识别的 document.styleSheets 后,还需要操作:
-
转换样式表中的属性值,使其标准化:比如颜色值都转换为 rgb 格式,em、rem 转换成 px -
先继承父节点样式,然后进行补充和覆盖
解析 JS
浏览器解析 HTML,当遇到 script 标签时,会立即执行 JS 脚本,停止解析文档,因为 JS 可能改动 DOM 和 CSS
-
如果遇到的是 script 标签内联代码,解析过程暂停,执行权限给到 Javascript 引擎,执行完再给渲染引擎继续解析
-
如果遇到的是外链脚本,会等待脚本下载完毕,再继续解析文档,为了减少时间损耗,可以借助 script 标签的2个属性
-
async 属性:立即请求文件,不阻塞渲染引擎,而是文件加载完后阻塞渲染引擎并立即执行文件内容 -
defer 属性:立即请求文件,不阻塞渲染引擎,等解析完 HTML 后再执行文件内容
布局
通过解析之后,浏览器需要进一步渲染页面,就需要进行布局
构建渲染树
DOM 树和 CSSOM 树合并成一棵渲染树
-
遍历 DOM 树的根节点,在 CSSOM 树上找到每个节点对应的样式 -
忽略不需要渲染(比如脚本标记、元标记)和不可见的节点(比如设置了 display:none ) -
添加需要显示的伪类元素到渲染树
计算元素布局
生成渲染树后,需要计算元素的大小及位置,包括字体大小、换行位置等,方便后续绘制过程,获取到每个元素的确切位置和大小
绘制
渲染器线程遍历渲染树,判断元素渲染层级顺序,创建绘制记录
若渲染树发生变化,浏览器触发:
-
重绘:重画一部分屏幕,元素几何尺寸不变,下面这些操作会导致重绘:
-
改变 color、background 相关属性 -
改变 outline 相关属性 -
改变 border-radius、visibility、box-shadow 等属性 -
重排:元素几何尺寸改变,重新验证和计算渲染树,成本比重绘高,下面这些操作会导致重排:
-
浏览器窗口大小发生变化 -
元素内容、尺寸、位置、字体大小等发生变化 -
查询某些属性或者调用某些方法 -
添加或者删除可见的 DOM 元素
为避免降低性能,尽量减少重绘与重排,可通过如下措施
-
操作 DOM 时,尽量在低层级的 DOM 节点进行操作 -
不要使用 table 布局 -
不要频繁操作元素样式,对于静态页面,可以修改类名,而不是样式 -
避免频繁操作 DOM,可通过创建 documentFragment,在其应用 DOM 操作后,再添加到文档 -
将元素先设置为 display:none,操作结束后再将其显示
光栅化
将计算后的信息(比如文档结构、元素样式、绘制顺序)转换为屏幕上的像素
页面布局变更,触发重排、重绘,则重新进行光栅化,影响性能;为此,浏览器采用合成方法,页面拆分成若干层,分别进行栅格化,再通过合成器线程合成页面,其过程如下:
-
主线程创建合成层,确定绘制顺序,将信息传递给合成器线程 -
合成器线程栅格化每个图层,将每个图块传递给光栅线程 -
光栅线程栅格化每个瓦片,将其存储在 GPU 内存 -
合成器线程通过 IPC 提交给浏览器进程,合成器帧传递给 GPU 进程,处理并显示在屏幕上
总结
-
浏览器由8个子系统组成 -
本文以 Chrome 浏览器为例,介绍其多进程和多线程架构 -
掌握浏览器页面渲染过程,有助于前端开发优化页面性能
文章出自:https://juejin.cn/post/7134251423381848072
作者:琳子
原文始发于微信公众号(前端24):浏览器页面渲染过程
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/216815.html