一、简介
1、浏览器进化路线
-
第一个是应用程序 Web 化
-
第二个是 Web 应用移动化
-
第三个是 Web 操作系统化
2、为什么需要学习浏览器工作原理?
-
准确评估 Web 开发项目的可行性
-
从更高维度审视页面
首屏的显示就涉及了 DNS、HTTP、DOM 解析、CSS 阻塞、JavaScript 阻塞等技术因素 -
在快节奏的技术迭代中把握本质—Node.js 是前端发展的一个核心推动力
二、浏览器的宏观架构
1、仅仅打开了1个页面,为什么有4个进程?
答: 因为打开 1 个页面至少需要 1 个网络进程、1 个浏览器进程、1 个 GPU 进程以及 1 个渲染进程,共 4 个;如果打开的页面有运行插件的话,还需要再加上 1 个插件进程。
-
线程 VS 进程
多线程可以并行处理任务,但是线程是不能单独存在的,它是由进程来启动和管理的。那什么又是进程呢?
- 线程是依附于进程的,而进程中使用多线程并行处理能提升运算效率。
- 1.进程中的任意一线程执行出错,都会导致整个进程的崩溃。
- 2.线程之间共享进程中的数据。
- 3.当一个进程关闭之后,操作系统会回收进程所占用的内存。
- 4.进程之间的内容相互隔离。
-
单进程浏览器
如此多的功能模块运行在一个进程里,是导致单进程浏览器不稳定、不流畅和不安全的一个主要因素
-
多进程浏览器
解决不稳定的问题:由于进程是相互隔离的,所以当一个页面或者插件崩溃时,影响到的仅仅是当前的页面进程或者插件进程
不流畅的问题:JavaScript 也是运行在渲染进程中的,所以即使 JavaScript 阻塞了渲染进程,影响到的也只是当前的渲染页面,而并不会影响浏览器和其他页面
安全问题:Chrome 把插件进程和渲染进程锁在沙箱里面,这样即使在渲染进程或者插件进程里面执行了恶意程序,恶意程序也无法突破沙箱去获取系统权限。
- 最新的多进程架构
- 浏览器进程。主要负责界面显示、用户交互、子进程管理,同时提供存储等功能。
- 渲染进程。核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该进程中,默认情况下,Chrome 会为每个 Tab 标签创建一个渲染进程。出于安全考虑,渲染进程都是运行在沙箱模式下。
- GPU 进程。其实,Chrome 刚开始发布的时候是没有 GPU 进程的。而 GPU 的使用初衷是为了实现 3D CSS 的效果,只是随后网页、Chrome 的 UI 界面都选择采用 GPU 来绘制,这使得 GPU 成为浏览器普遍的需求。最后,Chrome 在其多进程架构上也引入了 GPU 进程。
- 网络进程。主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里面的,直至最近才独立出来,成为一个单独的进程。
- 插件进程。主要是负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响。
-
多进程架构弊端
1、 更高的资源占用。
2、更复杂的体系架构。
-
总结
最初的浏览器都是单进程的,它们不稳定、不流畅且不安全,之后出现了 Chrome,创造性地引入了多进程架构,并解决了这些遗留问题。随后 Chrome 试图应用到更多业务场景,如移动设备、VR、视频等,为了支持这些场景,Chrome 的架构体系变得越来越复杂,这种架构的复杂性倒逼 Chrome 开发团队必须进行架构的重构,最终 Chrome 团队选择了面向服务架构(SOA)形式,这也是 Chrome 团队现阶段的一个主要任务。
2 、TCP协议:如何保证页面文件能被完整送达浏览器?
- 重传机制
- 数据包排序机制
在衡量 Web 页面性能的时候有一个重要的指标叫“FP(First Paint)”,是指从页面加载到首次开始绘制的时长。那什么影响 FP 指标呢?其中一个重要的因素是网络加载速度。
TCP 连接过程包括了建立连接、传输数据和断开连接三个阶段。
IP:把数据包送达目的主机
UDP:把数据包送达应用程序
TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。
相对于 UDP,TCP 有下面两个特点:
-
对于数据包丢失的情况,TCP 提供重传机制;
-
TCP 引入了数据包排序机制,用来保证把乱序的数据包组合成一个完整的文件。
TCP 为了保证数据传输的可靠性,牺牲了数据包的传输速度,因为“三次握手”和“数据包校验机制”等把传输过程中的数据包的数量提高了一倍。
-
总结
-
互联网中的数据是通过数据包来传输的,数据包在传输过程中容易丢失或出错。
-
IP 负责把数据包送达目的主机。
-
UDP 负责把数据包送达具体应用。
-
TCP 保证了数据完整地传输,它的连接可分为三个阶段:建立连接、传输数据和断开连接。
3、浏览器工作原理与实践
浏览器端发起 HTTP 请求流程
HTTP 是一种允许浏览器向服务器获取资源的协议,是 Web 的基础,正是建立在 TCP 连接基础之上的,HTTP 也是浏览器使用最广的协议
-
1.构建请求
浏览器构建请求行信息,构建好后,浏览器准备发起网络请求。
GET /index.html HTTP1.1
-
2.查找缓存
浏览器缓存是一种在本地保存资源副本,以供下次请求时直接使用的技术
- 3. 准备 IP 地址和端口
浏览器使用 HTTP 协议作为应用层协议,使用 TCP/IP 作传输层协议将它发到网络上,HTTP 的内容是通过 TCP 的传输数据阶段来实现的
你会发现在第一步浏览器会请求 DNS 返回域名对应的 IP。当然浏览器还提供了 DNS 数据缓存服务,拿到 IP 之后,接下来就需要获取端口号了。通常情况下,如果 URL 没有特别指明端口号,那么 HTTP 协议默认是 80 端口。
-
4.等待 TCP 队列
Chrome 有个机制,同一个域名同时最多只能建立 6 个 TCP 连接,如果超过6会排队等待。小于6,进入TCP连接状态
-
5.建立TCP连接
在 HTTP 工作开始之前,浏览器通过 TCP 与服务器建立连接
-
6.发送HTTP请求
一旦建立了 TCP 连接,浏览器就可以和服务器进行通信了。而 HTTP 中的数据正是在这个通信过程中传输的。
首先浏览器会向服务器发送请求行,它包括了请求方法、请求 URI(Uniform Resource Identifier)和 HTTP 版本协议。
-
7.服务器处理HTTP请求
(1)返回请求,包含响应行(协议版本、状态码)、响应头、响应体
curl -i https://time.geekbang.org/
(2)断开连接
通常情况下,一旦服务器向客户端返回了请求数据,它就要关闭 TCP 连接。不过如果浏览器或者服务器在其头信息中加入了:Connection:Keep-Alive,保持 TCP 连接可以省去下次请求时需要建立连接的时间,提升资源加载速度
Connection:Keep-Alive
(3 )重定向
-
curl -I geekbang.org```js
```
问题:
- 为什么很多站点第二次打开速度会很快?
主要原因是第一次加载页面过程中,缓存了一些耗时的数据(DNS 缓存和页面资源缓存这两块数据是会被浏览器缓存的)
服务器是通过什么方式让浏览器缓存数据的?
当服务器返回 HTTP 响应头给浏览器时,浏览器是通过响应头中的 **Cache-Control 字段来设置是否缓存该资源,**通过 Cache-Control 中的 Max-age 参数来设置缓存时长。
Cache-Control:Max-age=2000
如果缓存未过期会直接访问缓存中的资源,否则重发请求,在 HTTP 请求头中带上
If-None-Match:"4f80f-13c-3a1xb12a"
-
如果没有更新,就返回 304 状态码,相当于服务器告诉浏览器:“这个缓存可以继续使用,这次就不重复发送数据给你了。
-
”如果资源有更新,服务器就直接返回最新资源给浏览器。
缓存的细节查看
- 登录状态是如何保持的?
了解了缓存是如何工作的。下面我们再一起看下登录状态是如何保持的。前端将用户信息发给服务端,服务端验证成功并生成一段表示身份的字符串并写到响应头Set-cookie字段里, 然后把响应头传给浏览器,浏览器存到cookie中,并在下次访问的时候携带这个cookie,服务端根据判断是哪一个。客户端发来的连接请求,然后对比服务器上的记录,最后得到该用户的状态信息,通常需要在浏览器cookie设置httponly,提高安全性
Set-Cookie: UID=3431uad;
浏览器页面状态是通过使用 Cookie 来实现的
总结:
4、从输入URL到页面展示?----导航流程
- 浏览器进程–主要负责用户交互、子进程管理和文件储存等功能。
- 网络进程–是面向渲染进程和浏览器进程等提供网络下载功能。
- 渲染进程–主要职责是把从网络下载的 HTML、JavaScript、CSS、图片等资源解析为可以显示和交互的页面。因为渲染进程所有的内容都是通过网络获取的,会存在一些恶意代码利用浏览器漏洞对系统进行攻击,所以运行在渲染进程里面的代码是不被信任的。这也是为什么 Chrome 会让渲染进程运行在安全沙箱里,就是为了保证系统的安全。
从输入 URL 到页面展示
-
用户输入 —— 会判断是搜索内容还是URL,搜索内容会配合搜索引擎生成带搜索关键词的URL
离开当前页面时会有beforeunload事件,用户离开前可以执行一些操作
-
URL 请求过程
进入了页面资源请求过程,浏览器进程会通过进程间通信(IPC)把 URL 请求发送至网络进程,
网络进程会查找本地缓存是否缓存了该资源。如果有缓存资源,那么直接返回资源给浏览器进程;
如果在缓存 中没有查找到资源,那么直接进入网络请求流程。
这请求前的第一步是要进行 DNS 解析,以获取 请求域名的服务器 IP 地址。
如果请求协议是 HTTPS,那么还需要建立 TLS 连接。
利用 IP 地址和服务器建立 TCP 连接,
连接建立之后,浏览器端会构建请求行、请求头等信息,该域名相关的 Cookie 等数据附加到请求头中,
然后向服务器发送构建的请求信息。
服务器接收到请求信息后,会根据请求信息生成响应数据(包括响应行、响应头和响应体等信息),
并发给网络进程。等网络进程接收了响应行和响应头之后,就开始解析响应头的内容了
(1)重定向
在接收到服务器返回的响应头后,网络进程开始解析响应头,如果发现返回的状态码是 301 或者 302,那么 说明服务器需要浏览器重定向到其他 URL。这时网络进程会从响应头的 Location 字段里面读取重定向的地址,然后再发起新的 HTTP 或者 HTTPS 请求,一切又重头开始了。
在导航过程中,如果服务器响应行的状态码包含了 301、302 一类的跳转信息,浏览器会跳转到新的地址继续导航;如果响应行是 200,那么表示浏览器可以继续处理该请求
(2)响应数据类型处理
Content-Type 是 HTTP 头中一个非常重要的字段, 它告诉浏览器服务器返回的响应体数据是什么类型
```js
Content-Type:text/html
Content-Type:application/octet-stream // 字节流类型,通常浏览器会按下载类型处理
```
如果Content-Type判断为下载类型,那么浏览器会交给下载管理器,URL请求的导航流程结束,如果是HTML,会继续进行。
-
准备渲染进程
通常Chrome 会为每个页面分配一个渲染进程, 但是如果协议名和根域名相同,会几个页面共用同一个渲染进程
-
提交文档
就是指浏览器进程将网络进程接收到的 HTML 数据提交给渲染进程,具体流程是这样的:
- 首先当浏览器进程接收到网络进程的响应头数据之后,便向渲染进程发起“提交文档”的消息;
- 渲染进程接收到“提交文档”的消息后,会和网络进程建立传输数据的“管道”;
- 等文档数据传输完成之后,渲染进程会返回“确认提交”的消息给浏览器进程;
- 浏览器进程在收到“确认提交”的消息后,会更新浏览器界面状态,包括了安全状态、地址栏的 URL、前进后退的历史状态,并更新 Web 页面。
到这里,一个完整的导航流程就“走”完了,这之后就要进入渲染阶段了。
- 渲染阶段
导航流程总结
- 服务器可以根据响应头来控制浏览器的行为,如跳转、网络数据类型判断。
- Chrome 默认采用每个标签对应一个渲染进程,但是如果两个页面属于同一站点,那这两个标签会使用同一个渲染进程。
- 浏览器的导航过程涵盖了从用户发起请求到提交文档给渲染进程的中间所有阶段。
导航流程很重要,它是网络加载流程和渲染流程之间的一座桥梁,如果你理解了导航流程,那么你就能完整串起来整个页面显示流程,这对于你理解浏览器的工作原理起到了点睛的作用。思考时
5、从输入URL到页面展示?----渲染流程
DOM 生成、样式计算、布局、分层
看一下他们三者关系
按照渲染的时间顺序,流水线可分为如下几个子阶段:构建 DOM 树、样式计算、布局阶段、分层、绘制、分块、光栅化和合成
- 开始每个子阶段都有其输入的内容;
- 然后每个子阶段有其处理过程;
- 最终每个子阶段会生成输出内容。
(1)构建 DOM 树
这是因为浏览器无法直接理解和使用 HTML,所以需要将 HTML 转换为浏览器能够理解的结构——DOM 树
(2)样式计算(Recalculate Style)
- 把 CSS 转换为浏览器能够理解的结构——styleSheets。(document.styleSheets)
- 转换样式表中的属性值,使其标准化(把属性值转为计算值)
- 计算出 DOM 树中每个节点的具体样式,涉及到 CSS 的继承规则和层叠规则
(3)布局阶段
计算DOM 树中可见元素的几何位置,我们把这个计算过程叫做布局。
创建布局树
DOM 树中还含有很多不可见的元素,比如 head 标签,还有使用了 display:none 属性的元素,
显示之前还需要在额外的构建一棵只包含可见元素的布局树。
(过滤掉不可见元素,遍历可见元素)
布局计算
计算布局树节点的坐标位置, 布局树既是输入内容也是输出内容,没有清晰的分离,下一代LayoutNG
(4)分层
页面中如果有复杂效果,滚动或者z-index排序,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树
元素有了层叠上下文的属性或者需要被剪裁,满足其中任意一点,就会被提升成为单独一层。
(5)图层绘制
把一个图层的绘制拆分成很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表
(6)栅格化(raster)操作
绘制操作是由渲染引擎中的合成线程来完成的,
当图层的绘制列表准备好之后,主线程会把该绘制列表提交(commit)给合成线程
合成线程会将图层划分为图块
合成线程通过姗格化按照视口附近的图块来优先生成位图
栅格化过程都会使用 GPU 来加速生成,使用 GPU 生成位图的过程叫快速栅格化,或者 GPU 栅格化,
生成的位图 被保存在 GPU 内存中。
渲染进程把生成图块的指令发送给 GPU,然后在 GPU 中执行生成图块的位图,并保存在 GPU 的内存中。
(7)合成和显示
一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令——“DrawQuad”,然后将该命令提交给浏览器进程。
浏览器进程里面有一个叫 viz 的组件,用来接收合成线程发过来的 DrawQuad 命令,然后根据 DrawQuad 命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上。
渲染流水线大总结:
结合上图,一个完整的渲染流程大致可总结为如下:
- 渲染进程将 HTML 内容转换为能够读懂的 DOM 树结构。
- 渲染引擎将 CSS 样式表转化为浏览器可以理解的 styleSheets,计算出 DOM 节点的样式。
- 创建布局树,并计算元素的布局信息。
- 对布局树进行分层,并生成分层树。
- 为每个图层生成绘制列表,并将其提交到合成线程。
- 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。
- 合成线程发送绘制图块命令 DrawQuad 给浏览器进程。
- 浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上
重排(回流):
通过 JavaScript 或者 CSS 修改元素的几何位置属性,例如改变元素的宽度、高度等,那么浏览器会触发重新布局
需要更新完整的渲染流水线,所以开销也是最大的
重绘
如果修改了元素的背景颜色,那么布局阶段将不会被执行,因为并没有引起几何位置的变换,所以就直接进入了绘制阶段,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些。
直接合成阶段
CSS 的 transform实现动画效果,可以避开重排和重绘阶段,直接在非主线程上执行合成动画操作。这样的效率是最高的,因为是在非主线程上合成,并没有占用主线程的资源,所以相对于重绘和重排,合成能大大提升绘制效率
为什么减少重绘、重排能优化 Web 性能吗
-
使用 class 操作样式,而不是频繁操作 style
-
避免使用 table 布局
-
批量dom 操作,例如 createDocumentFragment,或者使用框架,例如 React
-
Debounce window resize 事件
-
对 dom 属性的读写要分离
-
will-change: transform 做优化