跳到主要内容

4.1-浏览器缓存机制

Create by fall on 22 Nov 2020 Recently revised in 10 Sep 2023

浏览器缓存

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

显而易见,因为缓存(将图片,css等静态资源保存到本地),将一些静态资源存储到本地,就不会再向服务端进行请求,而是从本地进行调用。

介绍

缓存分为服务端侧(Server Side,比如 Nginx、Apache)和客户端侧(Client Side,比如 web browser) 常用的服务端缓存有 CDN 缓存,客户端缓存就是指浏览器缓存。

网页加载中,耗时最多,最不稳定的,就是从服务器获取资源。

为什么使用浏览器缓存?

  • 请求更快:通过将内容缓存在本地浏览器或距离最近的缓存服务器(如CDN),在不影响网站交互的前提下可以大大加快网站加载速度。
  • 节省带宽:对于已缓存的文件,可以减少请求带宽甚至无需请求网络。
  • 降低服务器压力:在大量用户并发请求的情况下,服务器的性能受到限制,此时一些静态资源不会多次请求,可以起到均衡负载的作用,降低服务器的压力。

缓存的种类

  • 数据库缓存:当Web应用关系复杂,数据表爆炸式增长时,可将查询后的数据放到内存中进行缓存,下次查询时,直接在内存中获取,以提高响应速度。Memory Cache
  • CDN 缓存:当我们发送一个 Web 请求时,CDN 会帮我们计算器哪里得到这些内容的路径快且短
  • 代理服务器缓存:跟浏览器缓存性质类似,但是代理服务器缓存面向对象更广,规模更大,他不止为一个用户服务,一般为大量用户提供服务,同一个副本会被重用多次,因此在减少响应时间和带宽方面
  • 浏览器缓存,每个浏览器都实现了HTTP缓存,我们通过浏览器使用HTTP协议与服务器交互的时候
    • 缓存过程:强制缓存,协商缓存
    • 缓存位置:Service Worker -> Memory Cache -> Disk Cache -> Push Cache

缓存机制

缓存机制包括:强制缓存,协商缓存,执行顺序如下

  • 先判断强制缓存:浏览器发送请求前,根据请求头的 ExpiresCache-Control 判断是否命中强制缓存策略(包括是否过期),如果命中,直接从缓存获取资源,不会再请求服务器
  • 再判断协商缓存:没有命中强制缓存规则,浏览器会发送请求,根据请求头的 Last-ModifiedEtag 判断是否命中协商缓存,如果命中,直接从缓存获取资源。
  • 如果前两步都没有命中,则直接从服务器获取资源。
强制缓存协商缓存
是否会请求服务器不会向服务器发送请求先请求一下服务器询问该缓存是否可用
读取内容位置客户端本地客户端本地
返回状态码200缓存可用则返回 304,不可用则重新请求
相关响应头Expires**、**Cache-ControlLast-Modified、If-Modified-Since、Etag、If-None-Match

强制缓存

强制缓存有两个相关头字段

  • 通过绝对时间判断:Expires,给一个时间点,时间点内都会命中强制缓存
  • 通过相对时间判断:Cache-Control,比如两个小时,两个小时内,就会命中强制缓存

Expires

用来设置资源的过期时间的头字段,浏览器通过将其与当前本地时间对比,判断资源是否过期,在设置时间之后则过期。

例如:Expires: Sun, 20 Jun 2021 02:19:39 GMT

缺点:Expires 是服务器下发的绝对时间,无法保证客户端与服务器时间的一致性

Cache-Control

指定请求和响应遵循的缓存机制。在请求消息或响应消息中设置 Cache-Control 并不会修改另一个消息处理过程中的缓存处理过程。

Cache-Control

它是通用消息头字段,被用在 http 请求和响应中,通过设置指定指令实现缓存机制。

Cache-Control 响应头包括以下字段:

最重要的指令是 max-age=<seconds>,例如 Cache-Control: max-age=31536000 设置了一年后过期

max-age 以秒为单位,设置的是相对时间,表示距离发起请求的时间的秒数。无论本机与服务器时间是否一致,最后以相对时间为准;在此时间内不会再去请求服务器,直接去浏览器拿缓存。

其他取值为:

  • public:指示响应可被任何缓存区缓存
  • privite:指示对于单个用户的整个或部分响应消息,不能被共享缓存处理。这允许服务器仅仅描述当用户的部分响应消息,此响应消息对于其他用户的请求无效
  • no-cache:请求或响应消息不能缓存,客户端有缓存资源,但是否缓存需要协商缓存进行验证。
  • no-store:用于防止重要的信息被无意的发布。在请求消息中发送将使得请求和响应消息都不使用缓存。不使用任何缓存
  • min-fresh:指示客户机可以接收响应时间小于当前时间加上指定时间的响应
  • max-stale 指示客户机可以接收超出超时期间的响应消息。如果指定 max-stale 消息的值,那么客户机可以接收超出超时期指定值之内的响应消息。
  • no-transformmust-revalidateproxy-revalidate 等等
Cache-Control: private, max-age=0, no-cache

完整的缓存指令 Cache-Control

请求头 Cache-Control

请求时的缓存指令包括 only-if-cached

Expires 和 Cache-Control 两者之间的关系:

  • Expires:是 HTTP/1.0 协议中的
  • Cache-Control:HTTP/1.1 中新增,来定义缓存过期时间

在实际项目中会同时使用两个缓存策略

HTTP/1.0 还有一个功能比较弱的缓存控制机制:Pragma。

请求中包含 Pragma 的效果跟在头信息中定义 Cache-Control: no-cache 是相同的。使用 Pragma 时将忽略 Expires 和 Cache-Control 头,通常定义 Pragma 是为了向后兼容基于HTTP/1.0的客户端。

协商缓存相关头字段

协商缓存共有两种判断方式,有相关的四个缓存头

  • 使用时间判断:Last-ModifiedIf-Modified-Since
  • 使用唯一标识符:EtagIf-None-Match

Last-Modified:浏览器第一次请求资源时,服务器返回资源的同时,会在响应头中添加 Last-Modified 字段,写明资源最后一次被修改的时间。

Last-Modified: Fri, 23 Oct 2020 10:49:15 GMT

浏览器获得了资源的最后一次修改时间

If-Modified-Since

再次进入该网页,浏览器再次发送请求时,会将上次的时间先写入If-Modified-Since ,询问服务器是否在该时间之后更新了内容,服务器接收后,会和Last-Modified对比,如果Last-Modified大于上一次修改时间(Last-Modified-Since) 会显示已经修改,重新返回资源并显示 200,否则返回 304,使用浏览器的缓存。

浏览器将获得的时间传给服务器,进行对比。

缺点:

  • 如果请求时间相同,一秒之中多次修改,就无法返回最新信息
  • 只要资源改变(服务器动态生成),就会改变 Last-Modified,而实质内容不变,就起不到缓存的作用

EtagIf-None-Match

Etag: 是由服务端生成的(一般采用 hash 算法生成),只要资源有变化,Etag就会重新生成。服务器返回资源的同时,会在响应头中添加该字段,浏览器会将 Etag 与资源缓存。

If-None-Match:浏览器再次请求同一个资源时,会在请求头中将上次的 Etag 的值写入到请求头的 If-None-Match 字段中。 然后,服务器会将 If-None-Match 的值与服务器上该资源的 Etag 字段进行对比。如果一致,则表示未修改,响应 304 状态码;如果匹配不上,响应 200 状态码,并返回最新数据。

并且该优先值会高于 Last ModifiedLast Modified Since

存储位置

缓存读取顺序

  1. 当有 Service Worker 时,浏览器会从 Service Worker 读取缓存
  2. 再去内存读取缓存,如果有,直接加载
  3. 如果内存没有,则从硬盘读取,如果有直接加载
  4. 如果硬盘也没有,那么就进行网络请求
  5. 获取到的资源缓存到硬盘和内存

不同的存储位置有各自的优先级,当查找的缓存都没有命中的时候,才会请求网络

  • Service Worker
  • Memory Cache
  • Disk Cache
  • Push Cache

Service Worker

Service Worker:运行在浏览器背后的独立线程,一般可以用来实现持续性缓存功能。

并且是实现 PWA(Progressive Web App) 的重要模块之一。Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。

Service Worker实现缓存涉及到请求拦截(拦截get,post请求),如果是http协议,容易被拦截,出现安全隐患所以传输协议必须为 HTTPS 协议,保障安全。

实现 Service Worker缓存一般分三步

  • 首先需要先注册 Service Worker
  • 然后监听到 install 事件以后就可以缓存需要的文件
  • 用户下次访问就可以通过拦截请求的方式查询是否存在缓存,存在缓存就可以直接读取缓存文件,否则向服务器请求数据

Service Worker 没有命中缓存时,需要去调用 fetch 函数获取数据。因为没有命中缓存,会根据缓存查找优先级去查找数据。

但是不管我们是从 Memory Cache 中还是从网络请求中获取的数据,浏览器都会显示我们是从 Service Worker 中获取的内容。

Memory Cache

Memory Cache 指内存中的缓存,主要包含当前中页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等。

读取内存中的数据比读取磁盘快,虽然高效,但是会随着进程的释放而释放,一旦关闭tab栏,缓存就会释放。

内存缓存在缓存资源时并不关心返回资源的 HTTP 缓存头 Cache-Control 是什么值,同时资源的匹配也并非仅仅是对URL做匹配,还可能会对 Content-Type,CORS 等其他特征做校验。

一般存储JS,字体,图片等

Session 也会存储在内存中

Disk Cache

Disk Cache 存储在硬盘中的缓存,读取速度慢,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。

根据 HTTP Herder 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。绝大部分的缓存都来自 Disk Cache。

一般存储CSS等

Push Cache

Push Cache(推送缓存)其它三种缓存都没有命中时,才会被使用。是HTTP/2中的内容。

推送缓存只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂,在 Chrome 浏览器中只有 5 分钟左右,它并非严格执行 HTTP 头中的缓存指令。

  • 推送:所有的资源都能被推送,并且能够被缓存。但是 Edge 和 Safari 浏览器支持较差。

  • Push Cache 中的缓存只能被使用一次

  • 可以给其他域名推送资源

  • 浏览器可以拒绝接受已经存在的资源推送

  • 一旦连接被关闭,Push Cache 就被释放

  • 可以推送 no-cache 和 no-store 的资源

  • 多个页面可以使用同一个 HTTP/2 的连接,即使用同一个Push Cache。出于对性能的考虑,部分浏览器会对相同域名但不同的tab标签使用同一个HTTP连接。主要依赖浏览器的实现而定。

内存中读取顺序

内存中读取顺序(访问缓存优先级)

  • 先在内存中查找,如果有,直接加载
  • 如果内存中不存在,则在硬盘中查找,如果有直接加载
  • 如果硬盘中也没有,那么就进行网络请求
  • 请求获取的资源缓存到硬盘和内存

缓存的设置

Local Storage

  • 不设置过期时间可以实现永久存储
  • 收到同源策略影响
  • 最大可以存储 5M,因此被称作客户端的微型数据库,当然,现在很多浏览器都推出了存放大量数据的 indexDB
  • 只能存储字符串
  • 在隐私模式不可读
  • 本质上是对字符串的读取,读取内容过多(读取操作是同步的)会导致页面变卡

IE8 以下不兼容 Local Storage

使用

// 对象声明方式
localStorage.setItem('a','1')
localStorage.b = '2'
localStorage["c"] = '3'
localStorage.removeItem('a') // 清除 a 的数据
localStorage.clear() // 清除全部缓存

测试 localStorage 的兼容性

function storageTest(){
const testKey = 'test_key';
const testValue = 'test_value';
let isSupport = false;
try {
localStorage.setItem(testKey, testValue);
if (localStorage.getItem(testKey) === testValue) {
isSupport = true;
}
localStorage.removeItem(testKey);
return isSupport;
} catch(e) {
if (e.name === 'QuotaExceededError' || e.name === 'NS_ERROR_DOM_QUOTA_REACHED') {
console.warn('localStorage 存储已达上限!')
} else {
console.warn('当前浏览器不支持 localStorage!');
}
return isSupport;
}
}

Cookie 的详情可以在 3.2 章节查看

  • 会话跟踪技术
  • 存储重要的信息:购物车信息,视频是否点赞,网页是否浏览过,视频播放进度
  • 需要设置过期时间
  • cookie 的数据会自动的传递到服务器;
  • 最大可以存储 4 KB
  • 每一个域名下最多可以存储 50 条
  • 只能存储字符串
  • 格式:name=value;[expires=date];[path=path];[domain=xxx.com];[secure]
document.cookie = 'username=taly'
console.log(document.cookie)
// 存储中文的时候需要进行编码存储
document.cookie = 'username='+ encodeURIComponent('苍树人老师')
console.log(decodeURIComponent(document.cookie))
// 可以将过期时间设置为负值,然后就可以删除 cookie
// 快速设置cookie过期时间
document.cookie = 'username=xxx;expries='+new Date(0)
  • expires:过期时间,默认为会话,必须放置表示过期时间的 date 对象
  • path:默认值为当前路径,获取cookie值时,必须和下载文件的路径必须一致
  • domain:限制访问域名,如果加载当前页面的域名和设置的域名不一致,会设置失败
  • secure:如果不设置,可以通过 http 协议加载文件设置,或者https加载文件设置,设置之后只能通过https设置,增加了安全性

火狐浏览器支持本地缓存 cookie,谷歌 chrome 不支持本地加载的 cookie

Session Storage

详情可以看 3.2 章节

sessionStoragelocalStorage 的接口类似,但保存数据的生命周期与 localStorage 不同。 Session 这个词的意思,直译过来是"会话"。它只是可以将一部分数据在当前会话中保存下来,刷新页面数据依旧存在。但当浏览器关闭后,sessionStorage 中的数据就会被清空。一般结合后台使用。

  • 可以达到 5M 大小

用户的操作

地址栏访问,会触发浏览器的缓存操作

F5 刷新,浏览器会设置 max-age = 0 跳过强制缓存,进行协商缓存判断

ctrl + F5 会跳过所有的缓存机制,直接从服务器拉去资源

内容参考

作者连接
QLing09https://www.jianshu.com/p/bb116c7a74b3
descirehttps://juejin.cn/post/6857315310908506119
黑色的枫https://juejin.cn/post/6947936223126093861#heading-0
程序员cxuanhttps://juejin.cn/post/6844904115080790023