首先,我们带着这个问题去思考,想要知道这个问题的答案,不可避免的我们需要知道单进程架构和多进程架构的浏览器有哪些区别以及里面具体的架构模型是怎样的?
单进程架构
一句话概括:浏览器的所有功能模块都运行在同一个进程里。
上面这张图是网上流传很广泛的一张图,从上图可以看出所有的模块在一个进程中运行,不可避免的会导致浏览器不稳定、不流畅、不安全等问题。
不稳定?——主要原因在于插件和渲染引擎模块。
不流畅?——页面渲染、展现、JavaScript脚本、插件都运行在页面线程中,可能会导致页面卡死;内存泄露也是一个原因。
不安全?——主要原因在于插件和页面脚本。
多进程架构
我们来简单解释一下多进程架构是如何解决上述问题的。
稳定性:进程间相互隔离通过 IPC 机制进行通信,所有页面或插件的崩溃不会影响到主进程和其他页面。
流畅性:1个页面就是1个进程,关闭页面整个进程也关闭,不存在内存泄漏;JavaScript 运行在渲染进程中只会阻塞当前页面。
安全性:插件进程和渲染进程被锁进沙箱,进程锁导致即使存在恶意程序也无法突破沙箱去获取系统权限。
可以看到上图存在5种进程,下面我们来分别说明这5种进程的作用是什么:
浏览器进程: 主要负责界面显示、用户交互、子进程管理,同时提供存储等功能。
渲染进程: 核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页。
GPU 进程: GPU 最初是为了实现 3D CSS 的效果,现在也用来绘制 UI 界面。
网络进程: 负责页面的网络资源加载。
插件进程: 主要负责插件的运行,独立是为了防止插件崩溃对浏览器和其他页面造成影响。
当然,多进程架构还是存在一些问题:
- 资源占用过高,每个进程都包含了公共基础结构的副本(如 JavaScript 运行环境),意味着浏览器会消耗更多的内存资源
- 架构体系复杂,主要体现在浏览器各模块之间耦合性过高、扩展性较差
为了解决这些问题,Chrome 团队在 2016 年提出了 “面向服务的架构”(Services Oriented Architecture,SOA)思想。这个不再继续延展,感兴趣的可以自行网上搜索。
回到最开始抛出的问题
我们知道在多进程架构下,默认情况下每打开一个 Tab 页签,Chrome 都会为其创建一个渲染进程。按照道理来说,进程之间是相互独立的,资源也不会共享,那为什么会存在单个页面卡死,导致所有页面崩溃呢?
在默认情况下,如果打开一个标签页,那么浏览器会默认为其创建一个渲染进程。但是存在另一种情况——同一站点
什么是同一站点?两个URL的根域名+协议相同
如果从一个标签页中打开了另一个新标签页,当新标签页和当前标签页属于同一站点的话,那么新标签页会复用当前标签页的渲染进程。官方把这个默认策略叫process-per-site-instance。如下图leeCode网站:
为什么要让他们跑在一个进程里面呢? 因为在一个渲染进程里面,他们就会共享JS的执行环境,也就是说A页面可以直接在B页面中执行脚本。因为是同一家的站点,所以是有这个需求的。
但我在查看任务管理器时,发现了另外一种情况,也是从一个标签页中打开了新的标签页,这两个标签页同样是同一站点,但他们并没有共享渲染进程,这又是为什么?
去了解了一下发现是因为这两个标签页不在同一个浏览上下文组中,在控制台打印发现这两个标签页没有连接关系,也就说明不在同一浏览器上下文组。
而在之前的leetCode页面,发现他们存在连接关系,也就是为什么两个标签页共用一个渲染器进程。
那标签页是怎么建立连接关系的呢?
无非就是通过a标签页进行跳转或者通过window.open()打开新标签页,通过上述两种方式打开的新标签页,不论这两个标签页是否属于同一站点,他们之间都能通过 opener 来建立连接,所以他们之间是有联系的。在 WhatWG 规范中,把这一类具有相互连接关系的标签页称为浏览上下文组 ( browsing context group)。
在新打开的页面中,通过window.opener可以获取到源页面的部分控制权,即使新打开的页面是跨域也可以获取部分控制权。当a标签中加入了rel="noopener noreferrer"属性,就会window.opener会为null。这也就是为什么掘金网站的两个标签不共用一个渲染器进程的原因。
注意:同源策略对同一站点的限制,虽然 Chrome 会让有连接且属于同一站点的标签页运行在同一个渲染进程中,不过如果 A 标签页和 B 标签页属于同一站点,却不属于同源站点,那么你依然无法通过 opener 来操作父标签页中的 DOM,这依然会受到同源策略的限制。
从这个问题延伸
如果我分别打开两个标签页,即使是同一站站点,但他会分别使用不同的渲染进程。
因为第二个标签页中并没有第一个标签页中的任何信息,第一个标签页也不包含任何第二个标签页中的信息,所以他们不属于同一个浏览上下文组,因此即便他们属于同一站点,也不会运行在同一个渲染进程之中。
当然还有一种情况,我们可以试想一下,如果一个页面中存在iframe, iframe 也会遵守同一站点的分配原则,如果标签页中的 iframe 和标签页是同一站点,并且有连接关系,那么标签页依然会和当前标签页运行在同一个渲染进程中,如果 iframe 和标签页不属于同一站点,那么 iframe 会运行在单独的渲染进程中。