浏览器的多进程架构
进程是资源分配的最小单位,线程是CPU调度的最小单位。
在正式开始之前,我认为有必要以 Chrome 为例,介绍一下现代浏览器的多进程架构(multi-process architecture)
其中主要的部分有:
- 浏览器进程 (Browser Process),也叫主进程
- UI 线程(UI Thread),控制浏览器上的按钮和输入框等UI
- 网络线程(NetWork Thread),负责资源的下载
- 存储线程(Storage Thread),负责本地缓存文件的访问
- 渲染进程 (Renderer Process),也叫浏览器内核
- JS 引擎,负责执行 JavaScript,也是 JS 是单线程的由来
- GUI 渲染线程,负责渲染资源,与 JS 引擎互斥(一个运行一个挂起)
- 事件触发线程,管理事件循环 (鼠标点击、setTimeout、Mutation Observer、Ajax等),按顺序把事件放到 JS 执行队列
- 定时器线程,
setTimeout
并不是 JS 的功能,只是浏览器开给 JS 的一个接口 - 异步请求线程,处理
AJAX
请求,通过回调函数通知事件触发进程
- GPU进程,负责与 GPU 通信
- 第三方插件进程,就是我们安装的浏览器插件
说完浏览器的多进程架构,下面我们就正式开始讲浏览器从输入URL到页面展示到底发生了什么。
1. 构建请求
输入URL后,主线程中的UI进程接收到用户输入的URL,判断是query还是URL。
如果是搜索关键字,会将其拼接到默认搜索引擎的参数部分去搜索。这个流程需要对输入的不安全字符编码进行转义(安全字符指的是数字、英文和少数符号)。因为URL的参数是不能有中文的,也不能有一些特殊字符,比如= ? &
。(会被认为是URL本身的分隔符=
而产生歧义)
URL对非安全字符转义时,使用的编码叫百分号编码,因为它使用百分号加上两位的16进制数表示。这两位16进制数来自UTF-8编码,将每一个中文转换成3个字节。
我们经常会用的encodeURI
和 encodeURIComponent
正是起这个作用的,它们的规则基本一样,只是= ? & ; /
这类URI组成符号,这些在encodeURI
中不会被编码,但在encodeURIComponent
中统统会。因为encodeURI
是编码整个URL,而encodeURIComponent
编码的是参数部分。
(与之相反的解析方法即decodeURI
&decodeURIComponent
)
如果是URL,会把不完整的URL合成完整的URL。一个完整的URL应该是:协议+主机+端口+路径[+参数][+锚点]
。比如我们在地址栏输入www.baidu.com
,浏览器最终会将其拼接成https://www.baidu.com/
,默认使用443端口。
然后将URL转发给网络线程,网络线程会构建请求行信息,构建好后,浏览器就准备发起网络请求。
// 请求方法是GET,路径为根路径,HTTP协议版本为1.1
GET / HTTP/1.1
2. 查找强缓存
浏览器在发起真正的网络请求之前,会先检查浏览器的强缓存,如果命中,直接返回对应资源文件的副本。否则进入下一步。
2.1 什么是强缓存
浏览器的缓存策略分为强缓存和协商缓存,他们之间的根本区别在于是否需要发请求。
简单来说,就是强缓存就是你本地文件(保存在硬盘或者内存中),你可以立马访问到。memory cache
是指从资源从内存中被取出,disk cache
是指从磁盘中被取出;从内存中读取比从磁盘中快很多,但资源能不能分配到内存要取决于当下的系统状态。通常来说,刷新页面会使用内存缓存,关闭后重新打开会使用磁盘缓存。
协商缓存是需要你发请求给服务器,问问资源是否有更新,如果没有更新就访问本地缓存;如果有更新,服务器就会返回更新后的资源文件。
2.2 强缓存的实现
在 HTTP/1.0 时代,强缓存是通过HTTP响应头的Expires字段实现的。Expires表示绝对的失效时间,例如Expires:Wed, 05 Apr 2020 00:55:35 GMT
。浏览器通过这个字段的时间和用户的本地时间做比较来判定是否要读取缓存中的资源副本。这就可能导致一个问题:用户可以自己修改本地时间,使缓存失效。
所以在 HTTP/1.1 时代中新加入了 Cache-Control 字段来解决这个问题,通过设置cache-control: max-age=XXX
,可以实现缓存在 XXX 秒后过期(相对时间),这样就规避了用户可以自己篡改本地时间使缓存失效的问题。
在cache-control
和Expires
同时存在时,以cache-control
优先。
更多cache-control指令:
-
可缓存性
public
表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存,即使是通常不可缓存的内容。(例如