01 | Chrome架构:仅仅打开了1个页面,为什么有4个进程?

浏览器进化史

单进程浏览器时代

浏览器的所有功能模块都是运行在同一个进程里

image.png
带来的问题:

  • 不稳定
    • 早期浏览器通过插件实现视频、web游戏等功能,插件的崩溃会导致整个浏览器的崩溃
    • 复杂的JS代码可能导致渲染引擎模块的崩溃
  • 不流畅
    • 所有页面的渲染模块、JS执行环境和插件都是运行在同一个线程中,意味着同一个时刻只能有一个模块可以执行,如果有一个无限循环的脚本,导致其他任务没有机会执行,会导致浏览器失去响应,变卡顿
    • 页面内存泄漏 可能导致浏览器无法完全回收内存,导致越用越卡顿
  • 不安全
    • 插件或者脚本可以通过获取操作系统任意资源,引发安全性的问题

多进程浏览器时

  • 早期多进程架构

image.png
多进程架构主要是为了解决单进程架构模式下的几个问题:

  • 对于不稳定问题
    • 将进程相互隔离,当一个页面或者插件崩溃时,只影响当前页面的进程或者插件进程,并不会影响其他的页面
  • 对于不流畅问题
    • 如果有死循环的脚本,只会导致当前页面的渲染进程没有响应,不会影响其他页面
    • 关闭页面时,整个渲染进程也关闭了,所占用的内存都被系统回收
  • 对于不安全问题
    • 增加安全沙箱(sandbox)
      • 目前多进程架构

image.png

  • 浏览器进程
    • 负责显示界面显示、用户交互、子进程管理、存储功能等
  • 渲染进程(渲染进程合成的页面/图片,通过IPC发送给浏览器主进程展示
    • 核心任务是将HTML、CSS、JS转换为用户可以与之交互的网页,排版引擎Blink和JS引擎V8都是运行在该进程中的
  • GPU进程
    • 初衷是为了3D CSS的效果,目前网页、Chrome UI界面都选择用GPU绘制
  • 网络进程
    • 网络资源的加载,之前是浏览器进程中的一个模块
  • 插件进程
    • 负责插件的运行

带来的问题:

  • 更高的资源占用
  • 更复杂的体系架构

    未来面向服务的架构(SOA Services Oriented Architecture)

image.png

精选问答

  • 即使是如今的多进程架构,我偶尔还会碰到一些由于单个页面卡死最终崩溃导致所有页面崩溃的情况,请问这是什么原因呢

    作者回复: 是这样的,通常情况下是一个页面使用一个进程,但是,有一种情况,叫”同一站点(same-site)”,具体地讲,我们将“同一站点”定义为根域名(例如,geekbang.org)加上协议(例如,https:// 或者http://),还包含了该根域名下的所有子域名和不同的端口,比如下面这三个:

    https://time.geekbang.org https://www.geekbang.org https://www.geekbang.org:8080 都是属于同一站点,因为它们的协议都是https,而根域名也都是geekbang.org。你也许了解同源策略,但是同一站点和同源策略还是存在一些不同地方,在这里你需要了解它们不是同一件事就行了。

    Chrome的默认策略是,每个标签对应一个渲染进程。但是如果从一个页面打开了新页面,而新页面和当前页面属于同一站点时,那么新页面会复用父页面的渲染进程。官方把这个默认策略叫process-per-site-instance。

    直白的讲,就是如果几个页面符合同一站点,那么他们将被分配到一个渲染进程里面去。

    所以,这种情况下,一个页面崩溃了,会导致同一站点的页面同时崩溃,因为他们使用了同一个渲染进程。

    为什么要让他们跑在一个进程里面呢?

    因为在一个渲染进程里面,他们就会共享JS的执行环境,也就是说A页面可以直接在B页面中执行脚本。因为是同一家的站点,所以是有这个需求的


  • 感觉挺好奇的,单进程浏览器开多个页面,渲染线程也只有一个吗?感觉一个页面开一个线程不是更合理吗?

    作者回复: 之前回答的有点笼统,下面是我整理过后的回答:

    首先这个问题提的很好,我们从IE6开始讲起,IE6时代,浏览器是单进程的,所有页面也都是运行在一个主线程中的,当时IE6就是这样设计,而且此时的IE6是单标签,也就是说一个页面一个窗口。

    这时候,国内有很多国产浏览器,都是基于IE6来二次开发的,而IE6原生架构就是所有页面跑在单线程里面的,意味着,所有的页面都共享着同一套JavaScript运行环境,同样,对于存储Cookie也都是在一个线程里面操作的。 而且这些国产浏览器由于需要,都采用多标签的形式,所以其中的一个标签页面的卡顿都会影响到整个浏览器。

    基于卡顿的原因,国内浏览器就开始尝试支持页面多线程,也就是让部分页面运行在单独的线程之中,运行在单独的线程之中,意味着每个线程拥有单独的JavaScript执行环境,和Cookie环境,这时候问题就来了: 比如A站点页面登陆一个网站,保存了一些Cookie数据到磁盘上,再在当前线程环境中保存部分Session数据,由于Session是不需要保存到硬盘上的,所以Session只会保存在当前的线程环境中。这时候再打开另外一个A站点的页面,假设这个页面在另外一个线程中里面,那么它首先读取硬盘上的Cookie信息,但是,由于Session信息是保存在另外一个线程里面的,无法直接读取,这样就要实现一个Session同步的问题,由于IE并没有源代码,所以实现起来非常空难,国内浏览器花了好长一点时间才解决这个问题的。

    Session问题解决了,但是假死的问题依然有,因为进程内使用了一个窗口,这个窗口是依附到浏览器主窗口之上的,所以他们公用一套消息循环机制,消息循环我们后面会详细地讲,这也就意味这一个窗口如果卡死了。也会导致整个浏览器的卡死。

    国产浏览器又出了一招,就是把页面做成一个单独的弹窗,如果这个页面卡死了,就把这个弹窗给隐藏掉。

    这里还要提一下为什么Chrome中的一个页面假死不会影响到主窗口呢? 这是因为chrome输出的实际上图片,然后浏览器端把图片贴到自己的窗口上去,在Chrome的渲染进程内,并没有一个渲染窗口,输出的只是图片,如果卡住了,顶多图片不更新了。

    国产浏览器这一套技术花了四五年时间,等这套技术差不多成熟时,Chrome发布了 :(


目前的浏览器是多进程架构:浏览器主进程、渲染进程、网络进程、插件进程、GPU进程 项目配合完成资源下载、解析、渲染、绘制、展示的功能的。默认每一个Tab浏览器会分配一个渲染进程(一方面为了安全(不共享JS环境),一方面为了稳定(一个卡死不会影响另一个)但是如果多个页面是同一站点(协议、根域名)就会触发 process-per-site-instance 由多个页面共享同一个渲染进程(共享JS环境)

新开一个页面,至少要打开:浏览器主进程、渲染进程、网络进程、GPU进程

02 | TCP协议:如何保证页面文件能被完整送达浏览器?

一个数据包的“旅程”

1. IP:把数据包送达目的主机

IP: 网际协议(Internet Protocol,简称 IP)

image.png
简化的 IP 网络三层传输模型

2. UDP:把数据包送达应用程序

UDP 用户数据包协议(User Datagram Protocol)

image.png

3. TCP:把数据完整地送达应用程序

TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议

image.png
简化的 TCP 网络四层传输模型

完整的 TCP 连接过程

image.png
一个 TCP 连接的生命周期

  • 建立连接阶段
    • 三次握手(建立一个TCP连接时,客户端和服务器端要发送三个数据包以确认连接的建立)建立客户端和服务器端之间的连接
  • 传输数据阶段
    • 数据重发机制:接收端需要对每一个数据包进行确认操作,如果在规定的时间内发送端没有接受到反馈的确认信息,会触发重发机制
    • 数据包重排机制:大文件再传输过程中会被拆分成很多小的数据包,接收端接受到数据后,会按照TCP头中的序号为其排序,以保证数据的完整性。
  • 断开连接阶段
    • 四次挥手:保证双方都能断开连接

TCP VS UDP

UDP(User Datagram Protocol 用户数据包协议)

  • UDP不能保证数据的可靠性,但是传输速度非常快
  • 主要应用在一些关注速度、但是不那么严格要求数据完整性的领域,如在线视频、互动游戏等

TCP(Transmission Control Protocoa, 传输控制协议): 面向连接的、可靠的、基于字节流的传输层通信协议

  • 对于数据包丢失的情况,TCP 提供重传机制;
  • TCP 引入了数据包排序机制,用来保证把乱序的数据包组合成一个完整的文件。
  • 主要应用在一些关注数据传输可靠性的应用,如邮箱


精选回答

你怎么理解 HTTP 和 TCP 的关系?

HTTP协议和TCP协议都是TCP/IP协议簇的子集。

HTTP协议属于应用层,TCP协议属于传输层,HTTP协议位于TCP协议的上层。

请求方要发送的数据包,在应用层加上HTTP头以后会交给传输层的TCP协议处理,应答方接收到的数据包,在传输层拆掉TCP头以后交给应用层的HTTP协议处理。建立 TCP 连接后会顺序收发数据,请求方和应答方都必须依据 HTTP 规范构建和解析HTTP报文

03 | HTTP请求流程:为什么很多站点第二次打开速度会很快?

浏览器端发起 HTTP 请求流程 & 服务器端处理 HTTP 请求流程

image.png

问题解答

  1. 为什么很多站点第二次打开速度会很快?

主要是页面资源缓存DNS缓存
image.png
浏览器缓存

  • 强缓存:
    • 响应头:cache-control
    • 响应头:expires(过期时间)
  • 协商缓存:
    • 响应头:last-modifyed 请求头 if-modified-since
    • 响应头:Etag 请求头 if-none-match
    • 状态码:304

image.png
2. 登录状态是如何保持的?
image.png

04 | 导航流程:从输入URL到页面展示,这中间发生了什么?

image.png
从输入 URL 到页面展示

  1. 用户输入

    • 当用户输入后,判断是使用搜索引擎搜索内容,还是请求URL
    • 在当前页面即将被替换成新的页面之前,会执行 beforeUnload 事件,让用户清理一些数据或决定是否真的要离开当前页面
      1. window.addEventListener("beforeunload", function(e) {(e || window.event).returnValue = '页面正在进行操作,确定离开此页吗?'});
  2. URL 请求过程

  3. 准备渲染进程
    • 通常情况下,打开的新页面使用新的渲染进程
    • 如果从A打开的B页面和A是 “同一站点”, 则A、B复用渲染进程(process-per-site-instance)
  4. 提交文档

    提交文档:浏览器进程 网络进程 接收到的HTML提交给 渲染进程

image.png

  • 首先当浏览器进程接收到网络进程的响应头数据之后,便向渲染进程发起“提交文档”的消息;
  • 渲染进程接收到“提交文档”的消息后,会和网络进程建立传输数据的“管道”;
  • 等文档数据传输完成之后,渲染进程会返回“确认提交”的消息给浏览器进程;
  • 浏览器进程在收到“确认提交”的消息后,会更新浏览器界面状态,包括了安全状态、地址栏的 URL、前进后退的历史状态,并更新 Web 页面。

image.png
5. 渲染阶段
一旦文档被提交,渲染进程便开始页面解析和子资源加载了

精选回答

结合老师的讲义,自己总结了下,不考虑用户输入搜索关键字的情况:
1,用户输入url并回车
2,浏览器进程检查url,组装协议,构成完整的url
3,浏览器进程通过进程间通信(IPC)把url请求发送给网络进程
4,网络进程接收到url请求后检查本地缓存是否缓存了该请求资源,如果有则将该资源返回给浏览器进程
5,如果没有,网络进程向web服务器发起http请求(网络请求),请求流程如下:
5.1 进行DNS解析,获取服务器ip地址,端口(端口是通过dns解析获取的吗?这里有个疑问)
5.2 利用ip地址和服务器建立tcp连接
5.3 构建请求头信息
5.4 发送请求头信息
5.5 服务器响应后,网络进程接收响应头和响应信息,并解析响应内容
6,网络进程解析响应流程;
6.1 检查状态码,如果是301/302,则需要重定向,从Location自动中读取地址,重新进行第4步
(301/302跳转也会读取本地缓存吗?这里有个疑问),如果是200,则继续处理请求。
6.2 200响应处理:
检查响应类型Content-Type,如果是字节流类型,则将该请求提交给下载管理器,该导航流程结束,不再进行
后续的渲染,如果是html则通知浏览器进程准备渲染进程准备进行渲染。
7,准备渲染进程
7.1 浏览器进程检查当前url是否和之前打开的渲染进程根域名是否相同,如果相同,则复用原来的进程,如果不同,则开启新的渲染进程
8. 传输数据、更新状态
8.1 渲染进程准备好后,浏览器向渲染进程发起“提交文档”的消息,渲染进程接收到消息和网络进程建立传输数据的“管道”
8.2 渲染进程接收完数据后,向浏览器发送“确认提交”
8.3 浏览器进程接收到确认消息后更新浏览器界面状态:安全、地址栏url、前进后退的历史状态、更新web页面。

05、06 | 渲染流程:HTML、CSS和JavaScript,是如何变成页面的?

image.png
渲染流水线

渲染流水线大总结

image.png

  1. 渲染进程将 HTML 内容转换为能够读懂的 DOM 树** **结构。
  2. 渲染引擎将 CSS 样式表转化为浏览器可以理解的 styleSheets,计算出 DOM 节点的样式。
  3. 创建布局树,并计算元素的布局信息。
  4. 对布局树进行分层,并生成分层树
  5. 为每个图层生成绘制列表,并将其提交到合成线程。
  6. 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。
  7. 合成线程发送绘制图块命令 DrawQuad 给浏览器进程。
  8. 浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上。

相关概念

重排

更改元素的几何属性:宽度、高度

image.png
总结:重排需要更新完整的渲染流水线,所以开销也是最大的

重绘

更改元素的绘制属性:颜色、背景、vidibility、outline

image.png
总结:重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些。

合成

更改一个既不要布局也不要绘制的属性: translate

image.png
总结:相对于重绘和重排,合成能大大提升绘制效率。

如何减少重排、重绘

  1. 将DOM离线化
    • 将 dom 的 display: none
    • 通过Document.Fragment 创建一个DOM碎片
    • 复制节点,在副本上工作,然后替换它
  2. 缓存布局信息,读写分离 ```javascript // bad 强制刷新 触发两次重排 div.style.left = div.offsetLeft + 1 + ‘px’; div.style.top = div.offsetTop + 1 + ‘px’;

// good 缓存布局信息 相当于读写分离 var curLeft = div.offsetLeft; var curTop = div.offsetTop; div.style.left = curLeft + 1 + ‘px’; div.style.top = curTop + 1 + ‘px’;

  1. 3. **样式集中更改(通过class 或者 cssText 集中更改样式)**
  2. ```javascript
  3. //bad
  4. var left = 10;
  5. var top = 10;
  6. el.style.left = left + "px";
  7. el.style.top = top + "px";
  8. -------------------------------------------
  9. //good
  10. el.className += " className";
  11. //or
  12. el.style.cssText += "; left: " + left + "px; top: " + top + "px;";
  1. Debounce window resize 事件(避免多次触发重排)

CSS、JS会阻塞页面的渲染么

JS和CSS都有可能会阻塞DOM渲染 https://juejin.cn/post/6844903667733118983

css 不会阻塞DOM树的解析,但是会阻塞DOM树的渲染
JS会阻塞DOM树的解析和渲染

  • 情况1:当从服务器接收HTML页面的第一批数据时,DOM解析器就开始工作了,在解析过程中,如果遇到了JS脚本,如下所示:

    1. <html>
    2. <body>
    3. 极客时间
    4. <script>
    5. document.write("--foo")
    6. </script>
    7. </body>
    8. </html>

    那么DOM解析器会先执行JavaScript脚本,执行完成之后,再继续往下解析。

  • 情况2:我们内联的脚本替换成js外部文件

    1. <html>
    2. <body>
    3. 极客时间
    4. <script type="text/javascript" src="foo.js"></script>
    5. </body>
    6. </html>

    这种情况下,当解析到JavaScript的时候,会先暂停DOM解析,并下载foo.js文件,下载完成之后执行该段JS文件,然后再继续往下解析DOM。这就是JavaScript文件为什么会阻塞DOM渲染。

顺序 DOMContentLoaded 共同点 场景
async 加载优先顺序,先加载完成先执行 不相关,可能在 DOMContentLoaded 前或者后执行 加载这样的脚本都不会阻塞页面的渲染 页面脚本无关联关系的:广告、GA等
defer 文档顺序,在文档前面的先于后面的执行 相关,在DOMContentLoaded
之前执行完成
脚本之间存在依赖关系的
  • 情况3:外部CSS文件
    1. <html>
    2. <head>
    3. <style type="text/css" src = "theme.css" />
    4. </head>
    5. <body>
    6. <p>极客时间</p>
    7. <script>
    8. let e = document.getElementsByTagName('p')[0]
    9. e.style.color = 'blue'
    10. </script>
    11. </body>
    12. </html>
    当我在JavaScript中访问了某个元素的样式,那么这时候就需要等待这个样式被下载完成才能继续往下执行,所以在这种情况下,CSS也会阻塞DOM的渲染

css加载会阻塞js运行吗?

css加载会阻塞后面js语句的执行

  1. css加载不会阻塞DOM树的解析
  2. css加载会阻塞DOM树的渲染
  3. css加载会阻塞后面js语句的执行

提升CSS加载速度

  • CDN
  • 缓存
  • 压缩CSS资源
  • 合并CSS,减少HTTP请求

image.png
从上面两个流程图我们可以看出来,浏览器渲染的流程如下:

  1. HTML解析文件,生成DOM Tree,解析CSS文件生成CSSOM Tree
  2. 将Dom Tree和CSSOM Tree结合,生成Render Tree(渲染树)
  3. 根据Render Tree渲染绘制,将像素渲染到屏幕上。

从流程我们可以看出来

  1. DOM解析和CSS解析是两个并行的进程,所以这也解释了为什么CSS加载不会阻塞DOM的解析。
  2. 然而,由于Render Tree是依赖于DOM Tree和CSSOM Tree的,所以他必须等待到CSSOM Tree构建完成,也就是CSS资源加载完成(或者CSS资源加载失败)后,才能开始渲染。因此,CSS加载是会阻塞Dom的渲染的。
  3. 由于js可能会操作之前的Dom节点和css样式,因此浏览器会维持html中css和js的顺序。因此,样式表会在后面的js执行前先加载执行完毕。所以css会阻塞后面js的执行。

DOMContentLoaded VS onLoad

DOMContentLoaded: 当页面的内容解析完成后,则触发该事件

  • JS 会阻塞DOM的解析和渲染,所以DOMContentLoaded会在JS执行后触发
  • 因为CSS会阻塞JS执行
    • 如果JS在CSS之前或只有CSS资源,则DOMContentLoaded事件不必等CSS加载完毕
    • 如果页面同时存在JS和CSS且CSS在JS之前,那DOMContentLoaded事件需等待CSS加载完毕后触发

onLoad: 等待页面的所有资源都加载完成才会触发,这些资源包括css、js、图片视频等