我们都知道前端工程师每天都需要和浏览器打交道,浏览器对于我们的重要程度不言而喻。但是不知道各位在日常开发(摸鱼 bushi)中有没有和笔者有一些共同的疑惑,比如为什么我只是打开了一个标签页却开了好几个进程(可通过浏览器的任务管理器查看)、为什么大家都倾向于使用 chrome而不是其他浏览器、为什么同一个资源第二次、第三次访问往往会比第一次快很多、为什么资源可以完整安全地送达到浏览器、从输入一个 URL
到看到渲染好地页面这中间到底发生了什么?如果你对于上面的问题都能回答上来,那你对于浏览器的知识储备应该比我强不少哈哈哈,如果你对于这些问题也有疑问,那我相信这篇文章或许可以解答你的疑惑!!!接下来我们就按照问题一个一个来解决大家的疑惑并学习背后的知识原理。(ps:
先 赞
后看,这是我创作的动力!!!
你将学到:
- 浏览器的工作原理
- chrome的进程、线程相关知识和架构
- 浏览器缓存
- TCP/HTTP 相关知识
- 浏览器的导航过程(从输入
URL
到渲染出完整的页面
)
话不多说,让我们进入今天的学习吧!
chrome 的进程、线程以及架构
如果你现在打开浏览器的任务管理器,就会看到下面这样的图片
你可能会产生疑惑,明明我只是打开了掘金,明明 js
是单线程的,为什么会开了这么多的进程?为了解开这个疑惑,我们需要先看下 线程
和 进程
的基本概念
进程:系统进行资源调度和分配的基本单位,实现了操作系统的并发
线程:进程的子任务,是CPU
调度和分派的基本单位,用于保证程序的实时性,实现 进程
内部的并发
看完 进程
和 线程
的概念,接下来我们来看下 chrome
的多进程架构图
可以看到,chrome
包含以下进程
- 插件进程:负责插件的运行(隔离插件进程和其他进程可以防止因为插件进程的奔溃或错误影响浏览器和其他页面,因为你不能保证你的插件是绝对安全稳定的)
- 渲染进程:将
HTML/CSS/JS
转换为交互的页面(排版引擎Blink
和 JavaScript 引擎V8
都是运行在这里),默认情况下chrome
会为每个Tab
开启一个渲染进程(渲染进程通过在沙箱中运行来保证安全) - 网络进程:负责网络资源的加载,比如请求和响应,浏览器缓存等功能
- 浏览器主进程:负责显式、交互、子进程管理以及存储等功能
- GPU 进程:实现
3DCSS
等效果以及UI界面
的绘制
看完上面对各进程的知识讲解,你现在应该可以知道为啥我们打开一个网页会开启这么多进程了吧,关于这些进程一些实际应用场景将在后面的几个小节中穿插详解。
Q:为什么 JS 是单线程
的,为什么不适用多线程的并行能力?
A:由于 JS 经常要操作 DOM
以及和用户互动,如果是多线程的话,可能会出现 A 线程在修改 DOMA
结果 B 线程删除了 DOMA
这样的情况,这个时候就会出现线程冲突(HTML5
提出了 Web Worker
允许 js 创建多个子线程在后台进行并行操作,但是这些子线程并不能操作 DOM
所以这个标准并不能说让 js 变成多线程),而为了使 js 也能像其他语言那样拥有异步和并行的能力,浏览器通过 事件队列、事件循环
来解决这个问题,推荐看这篇文章 一文带你读懂浏览器的事件循环机制 - 掘金 (juejin.cn)
Chorme
团队正在设计新的Chrome
架构,目的是构建一个更内聚、松耦合、易于维护和扩展的系统
,感兴趣的朋友可以去网上查找相关资料
浏览器缓存
你在日常生活中是否有留意过这样一个现象,当我们第一次打开一个网站时,往往需要等待数秒的加载,而之后我们再打开这个网站时,就会比之前快很多,这其中其实是 浏览器缓存
的功劳。
浏览器
是指浏览器根据 url
第一次访问资源之后,根据缓存策略将 html、css、js、图片等资源
保存在浏览器本地,之后你再访问这些资源时,便可以根据缓存策略决定是使用本地的缓存还是重新请求。
浏览器缓存
是前端性能优化必不可少的重要一环!!!
那如何查看自己的请求是使用缓存还是重新访问呢?我们直接打开掘金的首页并打开控制台
(红色框里的就是用了memory cache 或disk cache 的 像这些就是使用了缓存的)
缓存分类:
1.Memory Cache:在浏览器内存中的缓存,访问优先级最高,访问速度也是最快的(和渲染进程共存,进程关闭就清除缓存,所以有效期比较短)
2.service worker Cache:独立的 js 线程,可实现离线缓存、消息推送等功能
3.Disk Cache:磁盘中的缓存,容量较大,但是相对的访问速度较慢
4.push Cache:HTTP2
的新特新,优先级最低(前面三种缓存无法命中后才会命中这个)
缓存策略:
浏览器的缓存策略可以分为强缓存 和协商缓存两种
- 强缓存: 不用向服务器请求。可以通过设置
HTTP Header
的expries
和cache-control
实现expries
: 服务器第一次响应时,会将过期的时间戳写在expries
的请求头中返回,当我们再次请求时,如果本地时间小于expries
的时间戳,就直接去缓存中读取资源- 缺点:如果更改了本地时间,可能会导致缓存失效
cache-control
: 通过max-age
控制本地资源的有效期,s-maxage
控制代理服务器(CDN缓存
)的有效期(s-maxage
仅在代理服务器中生效,max-age
仅在客户端生效),设置成no-store
则表示拒绝一切形式的缓存- 缺点:如果更改了本地时间,同样会导致缓存失效,但是解决了
expires
会因为客户端和服务器直接时差不一导致的缓存时间不一致的问题
- 缺点:如果更改了本地时间,同样会导致缓存失效,但是解决了
1 `cache-control`优先级高于 `expries
- 协商缓存:浏览器需要先向服务器发起请求,在判断是否从本地获取缓存资源。可以通过设置
HTTP Header
的Last-Modified
和ETag
实现Last-Modified
:首次响应时会返回Last-Modified
字段,之后客户端每次请求都会带上If-Modified-Since
的字段(上一次response
中的Last-Modified
的值),服务器根据该时间戳和资源在服务器上最后的修改时间是否一致来判断是否使用缓存(返回304
表示使用本地缓存,返回新的Last-Modified
表示不使用缓存)- 缺点:如果服务器没有及时感知到文件的变化(比如在毫秒级时间内完成了改动,但是因为
Last-Modified
是秒级导致无法感知)
- 缺点:如果服务器没有及时感知到文件的变化(比如在毫秒级时间内完成了改动,但是因为
ETag
:既然上面的Last-Modified
存在着缺点,那ETag
就是他的解决方案(服务器根据文件内容编码生成唯一标识符,可以精确感知文件的变化),首次响应会返回ETag
标识,下一次请求就带上If-None-Match
与服务端进行比较来判断是否使用缓存- 缺点:服务端需要额外的开销来生成
ETag
标识
- 缺点:服务端需要额外的开销来生成
Etag 在感知文件变化上比 Last-Modified 更加准确,优先级也更高。当 Etag 和 Last-Modified 同时存在时,以 Etag 为准。
HTTP 决策
关于 HTTP 请求的决策,这里我们通过Chrome给出的官方流程图来给大家讲解
接下来我给大家解释下上面的步骤
- 考虑资源是否可复用
- 如果不可以复用,则直接设置
Cache-Control
成no-store
表示拒绝一切形式的缓存 - 如果可以复用,考虑是否设置成每次都需要向浏览器进行缓存的有效确认
- 如果需要,则设置
Cache-Control
为no-cache
- 如果不需要,则考虑是否可以被代理服务器缓存
- 如果可以,设置成
public
- 如果不行,设置成
private
- 如果可以,设置成
- 如果需要,则设置
- 考虑资源有效时长
- 设置对应的
max-age
和s-maxage
等值 - 最后再设置协商缓存需要用到的
ETag
和Last-Modified
- 设置对应的
- 如果不可以复用,则直接设置
以上便是对于一个HTTP 请求是否要用缓存以及如何使用缓存的决策过程,我们在考虑是否使用缓存的时候也可以按着这个思路来考虑。
网页刷新对缓存的影响
接下来我们看看网页刷新对各类缓存的影响,帮助大家更好地使用缓存
- 强制刷新: 直接从服务器请求,跳过强缓存和协商缓存(比如
Ctrl + F5
) - 地址栏回车或正常刷新:跳出强缓存,检查协商缓存
- 新窗口的 URL 访问: 按照正常的缓存策略,即先检查强缓存
上面就是对于浏览器缓存的一些讲解了,相信看到这里你一定对于浏览器缓存有一些新的或者更深入的了解。
TCP/HTTP 在浏览器中的使用
首先我们先学一些前置知识
IP
:计算机的地址(互联网协议地址)UDP
:支持一个无连接的传输协议,为程序提供了一种无需建立连接就可以发送封装的IP
数据包的方法。不能保证数据的可靠性,但是传输速度非常快(可用于一些对完整性要求不高的场景,比如视频、互动游戏等)TCP
:一种面向连接的、可靠的、基于字节流的传输层通信协议(连接到不同但互相连接的计算机通信网络的著计算机中的成对进程之间依靠TCP
提供的可靠的通信服务)HTTP
:超文本传输协议,通常运行在TCP
之上,通过浏览器和服务器进行数据交互、进行超文本(文本、图片、视频等)传输的规定
接下来我们讲一下 TCP
连接的过程(包含 建立连接 - 传输数据 - 断开连接
)
- 建立连接:通过三次握手来建立连接
- 客户端向服务端发送
SYN包
并等待服务器确认(客户端进入SYN-SENT
阶段) - 服务端收到客户端的
SYN包
后,对该包进行确认后结束LISTEN
阶段,并返回一段TCP
报文 随后进入SYN-RECV
阶段 - 客户端接受到服务端的
SYN + ACK
包之后,确认连接是正常的 从而结束SYN-SENT
阶段并发送最后一段报文 - 服务端接收到客户端确认收到服务器数据的报文后,明确连接是正常的,三次握手的过程完成
- 客户端向服务端发送
- 传输数据:客户端需要对每个数据包进行确认操作(接收到数据之后要发送确认数据包给服务端)所以当服务端在规定事件内没有接受到客户端的确认消息,就判断丢包并重发 (一个大的文件在传输的过程中会被拆封成多个小的数据包并在客户端按照 TCP 头部的序号排序重组成完整的数据)
- 断开连接:通过四次挥手来保证双方都断开连接
总结: TCP
牺牲了数据包的传输数量(三次握手和数据包校验机制大大提高了数据包的数量)来保证了数据传输的可靠性
浏览器中 HTTP 的请求流程
先用一张图来看一下浏览器中的 HTTP 请求所经历的各个阶段
接下来我们按照图中的流程来解释下里面的各个过程
构建请求(根据搜索框的信息构建请求 url)
- 如果是符合
url
的形式则根据对应的网络协议拼接成请求url
(比如我们输入juejin.cn
会被自动拼接为https://juejin.cn/
) - 如果是一些其他搜索内容,则根据对应的搜索引擎去拼接请求的
url
(比如输入掘金
则会自动拼接为https://www.google.com/search?q=%E6%8E%98%E9%87%91&oq=%E6%8E%98%E9%87%91&aqs=chrome..69i57j0i13j69i59j0i13j0i30j0i13i30l4j0i10i13i15i30.1257j0j15&sourceid=chrome&ie=UTF-8
)
- 如果是符合
查找缓存:根据缓存策略去判断是使用缓存还是重新请求资源
- 使用缓存:直接使用缓存,整个请求过程结束
- 重新请求:进入网络请求过程
查找 ip 地址和端口: 根据
DNS(域名系统)
查找对应的ip
,拿到ip
后就需要获取对应的端口(url
没有特别指明默认使用80端口
)等待 tcp 队列:由于 chrome对于每个域名的
TCP
连接数量有限制 (同个域名下最多只能建立 6 个连接)- 同一个域名下有大于 6 个请求同时发生,则需要等待
- 少于 6 个。则直接建立 TCP 连接
建立 TCP 连接:进行三次握手进行 TCP 连接,连接成功则进入下一阶段
发送 HTTP 请求:
- 浏览器向服务器发送 请求行 ,包括 请求方法、请求 URI、HTTP 版本协议(如果是像
post
这些方法,还需要在请求体
中将对应的数据发送过去) - 发送请求头,包括所使用的操作系统、浏览器内核、域名信息、cookie 等信息
- 浏览器向服务器发送 请求行 ,包括 请求方法、请求 URI、HTTP 版本协议(如果是像
服务器处理 HTTP 请求: 根据浏览器的请求信息返回相应的内容
- 服务器返回的数据格式包括响应行(包括协议版本和 1 状态码等)、响应头(包括生成数据的时间戳、数据类型以及 cookie 等信息)、响应体(客户端实际想要的大部分内容)
- 断开连接
- 正常情况下服务器返回了响应数据后就会请求端来
TCP
连接 - 如果浏览器/服务器在头部信息加入了
Connection:Keep-Alive
则不会断开连接(可以省去下次请求时建立连接的时间从而加快资源加载速度)
- 正常情况下服务器返回了响应数据后就会请求端来
- 重定向:如果服务器返回了
3xx
一般就是重定向 比如301
这个时候会在响应头的location
中带上重定向的地址,浏览器接收到后就会自动再请求重定向的地址 (比如你输入baidu.com
会被重定向到https://www.baidu.com
)
总结:以上便是浏览器中
HTTP
请求的全部过程了,经过了:构建请求、查找缓存、准备 IP 和端口、等待 TCP 队列、建立 TCP 连接、发起 HTTP 请求、服务器处理请求、服务器返回请求和断开连接
这八个阶段从输入 URL 到渲染完页面发生了什么
面试官: 说一说从输入 url 到页面渲染完成这个过程发生了什么?
我:。。。。。。
相信大家多多少少都看过上面这个问题,因为这是一道非常高频的经典面试题,面试官往往会从这道来考察你的基础知识(包括浏览器的进程架构、请求过程、渲染原理等等),接下来我们就一起来学习下这个过程,避免下次遇到的时候翻车!!
我们先回顾下第一小节的知识,浏览器中有
浏览器主进程、网络进程、渲染进程
,这三个进程便是这个过程的三大主力军,我们再来回顾下第二小节的内容浏览器缓存
,这会决定是否跳过请求资源的阶段,我们再回顾下上一小节的HTTP在浏览器中的应用
,这将决定哪个进程负责请求中的那个阶段,这么一看是不是发现这道题可以把前面的知识都连接起来!!!接下来进入我们的学习吧!
按照管理,我们先看一下这个过程的大致流程图
(作图使用的是https://www.iodraw.com/)
我们先带大家回顾下三个进程的各自职责是干嘛的
- 浏览器主进程:用户交互、主进程管理以及文件存储等功能
- 网络进程:面向渲染进程和浏览器主进程提供网络相关功能(包括浏览器缓存)
- 渲染进程:将
HTML/CSS/JS/图片等资源
解析成页面(运行在沙箱内保证系统的安全)
接下来我们按照图来讲解整个过程
用户输入,浏览器根据用户输入生成请求的 url 并发起请求
判断是否使用缓存:
如果有缓存资源,则直接将缓存资源返回给浏览器主进程
如果不使用缓存,则进入网络请求过程
- 进行 DNS 解析 获取对应的
ip
地址 (如果是HTPS
还要建立TSL
连接) - 建立 TCP 连接
- 构建请求头、请求行、请求体信息发起请求
- 服务器开始处理响应信息
- 浏览器接收到响应信息并分析请求头
- 如果带有重定向等信息 则重新请求重定向 url 指向的资源
- 否则接受响应并提示浏览器主进程准备渲染进程
- 判断响应信息的类型(
Content-Type
)- 如果是下载类型 则被提交到下载管理器 URL 请求的导航阶段结束
- 如果是
HTML
类型 则继续导航流程
- 接下来准备渲染流程
- 进行 DNS 解析 获取对应的
准备渲染流程
- 浏览器默认会为每个 tab 页创建一个渲染进程
- 如果是属于同一个站点(比如从一个页面打开另一个新的页面且两个页面是同一个站点)则新页面会复用父页面的渲染进程
- 准备好渲染进程后将进入提交文档阶段
提交文档阶段:将网络进程接收到的
HTML
数据提交给渲染进程- 浏览器进程接受到网络进程的响应头数据后 ,向浏览器进程发起
提交文档
的信息 - 渲染进程接收到
提交文档
的信息后 就会和网络进程建立起传输数据的管道
- 当文档数据传输完成后 渲染进程会返回
确认提交
的信息给浏览器进程 - 浏览器进程收到
确认提交
的信息后 会更新浏览器页面状态(包括安全状态、地址栏 url、历史状态以及 web 页面) 这就是为什么我们输入一个网址时浏览器并没有立马跳转而是过一会跳转的原因 - 之后就进入了渲染阶段
5.渲染阶段:渲染阶段即将
HTML/CSS/JS/图片
等资源渲染成我们看到的页面,这一部分的内容有点多,受限于篇幅,推荐大家直接看我的另一篇文章 浏览器是如何将 HTML、CSS、JavaScript 变成可视的页面的 - 掘金 (juejin.cn)总结: 好的,讲到这里我们已经将从输入
URL
到页面渲染的内容讲完了,相信大家都收获了一些东西结束语:
不知不觉已经写了好多,无奈涉及的知识太多无法一篇讲完,后续将继续更新浏览器原理知识系列,感兴趣的小伙伴可以点个关注一起学习!!!
往期推荐:
谈谈前端性能优化的常见手段及知识原理 - 掘金 (juejin.cn)
- 浏览器进程接受到网络进程的响应头数据后 ,向浏览器进程发起