浏览器
1. 浏览器结构
用户界面: 用于展示除标签页窗口之外的其他用户界面内容。
渲染引擎: 负责渲染用户请求的页面内容。
浏览器引擎:在用户界面和渲染引擎之间有个浏览器引擎,用于在用户界面和渲染引擎之间传递数据。
渲染引擎下面还有很多小的功能模块,比如负责网络请求的网络模块,用于解析和执行 js 的 js 解释器。还有数据存储持久层,帮助浏览器保存各种数据,比如 cookie 等等
渲染引擎 就是核心 ,就是内存 V8
2.线程和进程
进程是操作系统进行资源分配和调度的基本单元,可以申请和拥有计算机资源,进程是程序的基本执行实体。
线程是操作系统能够进行运算调度的最小单位,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
两个进程间需要传递某些数据,则需要通过进程间通信管道 IPC来传递
3.多进程浏览器结构
- 浏览器进程: 负责与浏览器的其他进程协调工作。
渲染器进程用来控制显示 tab 标签内的所有内容,浏览器在默认情况下会为每个标签页都创建一个进程 chromium 支持 4 中进程模式
4.浏览器渲染原理
在地址栏输入地址时,浏览器进程的 UI 线程会捕捉输入的内容,
如果是网址,则 UI 线程会启动一个网络进程来请求 DNS 进行域名解析获取数据;如果是要搜索,则会调用默认搜索引擎进行搜索
网络线程获取数据后,会通过 SafeBrowsing 来检查站点安全性
返回数据且校验通过后,网络进程会通知 UI 线程(浏览器进程),然后浏览器进程会创建一个渲染器进程(Renderer Thread),并将通过 IPC 管道将数据传给渲染器进程,进入渲染流程
渲染器主进程 将 html 进行解析,构建 dom 数据结构.
- HTML 首先经过 Tokeniser 标记化,通过词法分析,将输入 HTML 内容解析成多个标记,
- 根据标识后的标记进行 DOM 树构造,在 DOM 树构造过程中会创建 Document 对象
- HTML 代码会引入额外资源 CSS,js,图片等.
- 图片和 CSS,这些资源从网络下载或者缓存读取.且不会阻塞 HTML 的解析,因为不会影响 Dom 生成
- 遇到 script 标签,会停止 HTML 解析流程,转而加载并执行 js
- 这就是为啥 script 要放置在合适的位置,或者用 async 或者 defer 属性来异步加载执行 js
- HTML 解析完形成 DOM tree,但是此时还不知道 dom tree 上每个节点应该长什么样子
主线程解析 css 确定每个 Dom 节点的样式 ,CSSOM(CSS Object Model)Tree(或者叫 CSS 规则树)再根据 CSSOM 和 DOM 树构造渲染树 Render Tree(呈现树)。
接下来需要确定放置在页面的什么位置,就是坐标的节点,以及该节点需要占用多大的区域,这个阶段叫做 layout 布局
- 主线程遍历 DOMtree 和计算好的样式来生成 layout tree,layout tree 上的每个节点都记录 x,y 坐标和边框尺寸
- Dom tree 和 layout tree 并不是一一对应的: 设置 display:none 的节点不会出现在 layout tree 上,而在例如 before 等伪类中添加了 content 值的元素,content 的内容会出现在 layout tree,不会出现在 DOM 树里。
- 现在就知道了元素的大小,形状和位置.还要知道以什么样的顺序绘制,
为了保证在屏幕上展示正确的层级,在绘制阶段,主线程遍历 layout tree 创建一个绘制记录表,该表记录了绘制的顺序。
Chrome 使用了一种更复杂的栅格化流程,叫做 compositing 组合。Compositing 是一种将页面的各个部分分成多个图 层,分别对其进行栅格化并在合成器线程 compositor thread 的单独线程中进行合成页面的技术。
主线程 遍历 layout tree 生成 layer tree.
当 layer tree 生成完毕和绘制顺序确定后,
- 主线程将这些信息传递给 compositor 线程。合成器线程将每个图层栅格化。一层可能像页面的整个长度一样大,因此合成器线程将它们切分为多个图块,然后将每个图块发送给栅格线程
- 栅格线程栅格化每个图块并将他们存储在 GPU 内存中,对图块栅格化之后.合成器线程可以给不同的栅格线程的优先级,例如可视化区域优先处理
- 栅格化完成后,栅格线程将draw quads 的图块信息这些信息里记录包含各个图块在内存中的位置和在页面哪个, 传回合成器线程,
根据这些数据合成器线程生成了一个合成器帧 (frame).然后这个合成器帧 通过 ipc 传给浏览器线程
然后浏览器进程将 合成器帧 传到 GPU,Gpu 进程渲染到屏幕上
页面然后变化,比如你滚动了当前页面,则会生成一个新的 compositor frame,新的 frame 再传给 GPU。再次渲染到屏幕上
当改变元素尺寸时,会重新进行样式计算,布局,绘制,以及后面的所有流程。这种行为我们称为重排。当我们改变某个元素的颜色属性时,不会重新触发布局,但还是触发会样式计算和绘制,这个就是重绘。我们可以发现重排和重绘会占用主线程,还有一个东西的运行也是在主线程。对,js。既然他们都是在主线程就会出现抢占执行时间的问题。
当一帧的时间内布局和绘制结束后,还有时间,js 会占用主线程执行,
如果主线程执行过长,就会导致在下一帧开始时 js 没有归还主线程,导致下一帧动画没有按时渲染,就会出现卡顿
通过 requestAnimationFrame 解决 requestAnimationFrame 这个方法会在每一帧被调用,通过这个 api 的回调参数,我们可以知道每一帧当前还剩余的,我们可以把 js 运行任务分成一些小块,在时间用完前,归还主线程
第二种方式: 栅格化整个流程是不占用主线程的,只在合成器和栅格线程中运行,这就意味着它无需和 js 抢夺的主线程。刚才提到,如果我们反复重绘和重排,可能会导致掉帧,因为有可能会有 js 的执行阻塞了主线程。css 中有个动画属性叫 transform,通过该属性实现的动画,不会经过布局和绘制,而是直接运行在 Compositor 和 rasterizing 线程中,所以不会受到主线程中 js 执行的影响。更重要的是 transform 的动画,由于不需要经过布局绘制样式计算,所以节省了很多运算时间。可以让复杂的动画更加流畅。我们常常会哪些属性来实现动画效果呢,位置变化,宽高变化,那这些都可以使用 transform 来代替。