🐱 个人主页:不叫猫先生
🙋♂️ 作者简介:前端领域新星创作者、阿里云专家博主,专注于前端各领域技术,共同学习共同进步,一起加油呀!
💫系列专栏:vue3从入门到精通、TypeScript从入门到实践
📢 资料领取:前端进阶资料以及文中源码可以找我免费领取
🔥 前端学习交流:博主建立了一个前端交流群,汇集了各路大神,一起交流学习,期待你的加入!(文末有我wx或者私信)
🍬浏览器缓存机制与分类
🥙前言
处理好浏览器缓存对提升系统的性能有很大的帮助,为什么要使用缓存,我们一般请求资源后直接使用,当我们再次请求资源时,还要继续从服务器拿到数据吗?答案不以为然,当第一次请求资源后,可以进行缓存,然后再次请求资源时可以直接从缓存中读取,提高了效率。
🥪浏览器缓存分类
浏览器的缓存分为了很多种,大概分为以下几类:
按缓存位置:memory cache
, disk cache
, Service Worker
等
按失效策略:Cache-Control
,ETag
等
我们在Network的Size中会看B、KB、M
等,这些都是网络请求,而memory cache
、disk cache
等这些都是从缓存中读取。
浏览器请求的时候怎么进行缓存操作呢?具体如下:
操作的系统的一般都是先读内存,再读硬盘。请求资源遵循找到即返回,找不到则继续;优先级为:
- Service Worker
- memory cache(内存的缓存)
- disk cache(硬盘中的缓存)
- 网络请求
我们可以看到,有的是内存缓存,有的是硬盘缓存,那么两者有什么区别呢?
内存缓存(from memory cache):内存缓存具有两个特点,分别是 快速读取和时效性:
- 快速读取:内存缓存会将编译解析后的文件,直接存入该进程的内存中,占据该进程一定的内存资源,以方便下次运行使用时的快速读取。
- 时效性:一旦该进程关闭,则该进程的内存则会清空。
硬盘缓存(from disk cache):硬盘缓存则是直接将缓存写入硬盘文件中,读取缓存需要对该缓存存放的硬盘文件进行I/O操作,然后重新解析该缓存内容,读取复杂,速度比内存缓存慢 。
🌮按缓存位置分类
memory cache
memory cache 是内存中的缓存,它是浏览器为了加快读取缓存速度而进行的自身的优化行为。几乎所有网络请求都会自动加入到memory cache ,但是由于数量大和浏览器的占用内存不能无限扩大,故它属于短期存储。关闭浏览器tab便会失效;该页面的缓存占用内存超级多,则会在关闭tab前,排在前面的缓存便失效。
- 内存中的缓存
- 短期存储
- 网络请求几乎都会自动加入
preloader、preload
memory cache使用的话有两种方式.
- preloader
浏览器打开网页,先请求html,若有js、css等资源,会使用CPU资源进行解析并执行。之前是串行模式,即请求js/css-解析执行-请求下一个js/css-解析执行。我们会发现解析时候,网络请求是空闲的,使用preloader则可以在解析资源的同时,还能请求下一批资源 ,使用preloader请求来的资源会放到memory cache,供之后解析执行操作 - preload
显式的预加载资源,也会放到memory cache。比如<link rel="preload"></link>
memory cache保证了页面中两个相同请求,都实际请求最多一次,比如(两个src相同的<img>
,两个href相同的<link>
),匹配缓存时,不仅匹配相同的url,还会比较他们的类型、CORS中的域名规则等。脚本(script)类型被缓存的资源是不能用在(image)类型的请求中,即使他们的src
相等。
disk cache
disk cache 也叫做HTTP cache,属于硬盘上的缓存,允许跨会话,跨站点情况使用,比如;两个站点使用一张图片,持久存储在文件系统。通过HTTP头部信息判断资源是否缓存、是否仍可用,是否过时重新请求。比读取内存慢些,绝大部分缓存都来自disk cache。
- 持久存储
- 跨站点、快会话
- HTTP头部判断
Service Worker
Service Worker 是由开发者编写的额外的脚本,且缓存位置独立,出现也较晚,使用还不算太广泛。memory cache与disk cache都是通过浏览器内部判断,Service Worker可以直接操作缓存,储存在Application中的Cache Storage 。关闭浏览器或者tab缓存依然存在。除非手动调用cache.delete(resource)或者容量超出限制,被浏览器全清空。
Service Worker没能命中缓存,则会用fetch()
方法继续获取资源,此时memory cache或者disk cache进行下一次找缓存工作。Service Worker 的 fetch()
方法获取的资源,即便它并没有命中 Service Worker 缓存,甚至实际走了网络请求,也会标注为 ServiceWorker缓存。
网络请求
为了提升之后请求的缓存命中率,自然要把这个资源添加到缓存中去。
- 根据 Service Worker 中的 handler 决定是否存入 Cache Storage (额外的缓存位置)
- 根据HTTP头部字段(Cache-control,Pragma等)决定是否加入disk cache
- preloader、preload保存一份资源的引用
🍰按失效策略分类
强缓存
当客户端请求时,先访问缓存数据库看缓存是否存在,存在直接返回,不存在,请求真实服务器,响应后加入到缓存数据库。
强缓存,直接减少了请求数,是提升最大的缓存策略。 它的优化覆盖率请求数据的三个步骤。如果考虑缓存来优化页面性能,则强缓存应该被首选。
实现方式有两种:Expires
、Cache-contrl
Expires
这个是HTTP1.0字段,表示缓存到期时间,是绝对时间(当前时间+缓存时间),在响应消息头设置后,在未过期之前步需要再次请求。但是弊端很大:
Expires: Thu, 10 Nov 2017 08:45:11 GMT
- 修改本地时间、时差等都会造成客户端与服务端时间不一致
- 写法复杂
Cache-contrl
表示缓存的最大有效时间,该时间为相对时间。
Cache-control: max-age=2592000
- max-age:即最大有效时间
- must-revalidate:如果超过了 max-age 的时间,浏览器必须向服务器发送请求,验证资源是否还有效。
- no-cache:虽然字面意思是“不要缓存”,但实际上还是要求客户端缓存内容的,只是是否使用这个内容由后续的对比来决定。
- no-store: 真正意义上的“不要缓存”。所有内容都不走缓存,包括强制和对比。
- public:所有的内容都可以被缓存 (包括客户端和代理服务器, 如 CDN)
- private:所有的内容只有客户端才可以缓存,代理服务器不能缓存。默认值。
max-age
到期应该重新验证,no-cache
是必须验证,(max-age
,must-revalidate等价于no-cache
),Cache-control
优先级高于Expires
。
对比缓存
对比缓存也叫做协商缓存,当强制缓存失效(超过规定时间),就会使用对比缓存,由服务器决定缓存内荣是否失效。
浏览器先请求缓存数据库,返回一个缓存标识。之后浏览器拿这个标识和服务器通讯。如果缓存未失效,则返回 HTTP 状态码 304 表示继续使用,于是客户端继续使用缓存;如果失效,则返回新的数据和缓存规则,浏览器响应数据后,再把规则写入到缓存数据库
对比缓存在请求数和没有缓存是一致的,但是返回304,返回的仅仅是状态码,没有实际的文件内容,因此 节省了响应体体积,缩短网络传输时间。 和强制缓存来说提升幅度较小。对比缓存有两种方式:
Last-Modified & If-Modified-Since
1 服务器用Last-Modified
字段告知客户端资源最后一次被修改的时间
Last-Modified: Mon, 10 Nov 2018 09:10:11 GMT
- 浏览器储存这个值和内容
- 下一次请求相同资源时时,浏览器从自己的缓存中找出“不确定是否过期的”缓存。因此在请求头中将上次的 Last-Modified 的值写入到请求头的 If-Modified-Since 字段。
- 服务器会将 If-Modified-Since 的值与 Last-Modified 字段进行对比。如果相等,则表示未修改,响应 304;反之,则表示修改了,响应 200 状态码,并返回数据。
缺陷:
- 资源更新速度以秒以下单位则会失效,因为它的时间单位最低为秒
- 文件是服务器动态生成的,那么方法的更新时间永远都是生成时间,尽管文件未变化
Etag & If-None-Match
Etag & If-None-Match解决了上述问题。Etag 存储的是文件的特殊标识(一般都是 hash 生成的),服务器存储着文件的 Etag 字段。之后的流程和 Last-Modified 一致,只是 Last-Modified 字段和它所表示的更新时间改变成了 Etag 字段和它所表示的文件 hash,把 If-Modified-Since 变成了 If-None-Match。服务器同样进行比较,命中返回 304, 不命中返回新资源和 200。
- Etag 的优先级高于 Last-Modified
🍔总结
- 调用 Service Worker 的 fetch 事件响应
- 查看 memory cache
- 查看 disk cache。这里又细分:
- 如果有强制缓存且未失效,则使用强制缓存,不请求服务器。这时的状态码全部是 200
- 如果有强制缓存但已失效,使用对比缓存,比较后确定 304 还是 200
- 发送网络请求,等待网络响应
- 把响应内容存入 disk cache (如果 HTTP 头信息配置可以存的话)
- 把响应内容 的引用 存入 memory cache (无视 HTTP 头信息的配置)
- 把响应内容存入 Service Worker 的 Cache Storage (如果 Service Worker 的脚本调用了 cache.put())
本文参考:一文读懂前端缓存