缓存是浏览器性能优化中非常重要的一种方式,我们可知数据请求可以分为:发起请求、后端处理、浏览器响应三个步骤。而前端缓存主要是在“请求”和“响应”中进行。在请求步骤中,浏览器使用缓存就可以不用发送请求,从而减少请求次数;在响应步骤中,如果前后端数据一致,那么数据就无须再从后端传到前端,通过减少响应的内容从而减少传输时间。
附一张我感觉是超级无敌专业的思维导图:
一、缓存位置及分类
优先级由上至下:
Service worker
memory cache
disk cache
Service worker:是浏览器在后台独立于网页运行的脚本,类似于一个中间人,浏览器发出的请求都会被Service Worker拦截并被它处理。当有Service Worker时,浏览器会优先从Service Worker读取缓存。
memory cache是短期存储,页面刷新时(普通刷新,F5)通常是从内存中获取缓存;页面关闭,缓存就失效了,从内存中获取缓存比从硬盘中获取速度快。几乎所有的请求资源都能进入memory cache。浏览器为了加快读取缓存速度而进行的自身的优化行为,不受开发者控制也不受HTTP协议的约束。
从memory cache获取缓存内容时,浏览器会忽视max-age=0,no-cache等头部配置,如果完全不想让一个资源进入缓存,需要使用no-store。
disk cache是存在硬盘中的缓存,当页面关闭后再次打开时读取的就是硬盘缓存。
disk cache(HTTP cache)包括强制缓存(强缓存)和协商缓存(对比缓存):首先进行强制缓存,如果强缓存失效,需要使用对比缓存,由服务器决定缓存内容是否失效。
强制缓存:
当命中强制缓存时,浏览器不会去请求服务器,而是直接使用本地缓存资源,从Chrome开发者工具的Network可以看到返回200状态码,size一栏显示from memory cache或from disk cache。
从Network —> size一栏可以看到两类:
- 如果是显示数字,几k或者几M,则是表示是从网络传输的
- 其他的则是from ServiceWorker,from memory cache,from disk cache
强制缓存直接减少请求数,是提升最大的缓存策略
是否命中强制缓存是由头中的expires和cache-control控制的,通过HTTP Header设置Expires和Cache-Control
1、 expires是HTTP1.0时的标准,是表示服务器的过期时间,是格林威治时间,绝对时间。当请求的时候客户端的时间超过expires标识的时间时就会去请求服务器。
2、 但是expires存在的问题是:当用户手动修改了这个时间就会有问题
3、 Cache-control是HTTP1.1的产物,可以看作expires的补充
4、 判断缓存是否过期的步骤:看是否有cache-control的max-age;如果没有则用expires作为过期时间比较
Cache-control常用值:
- Max-age:缓存的最大有效时间,会覆盖expires。
- S-maxage:如果该值存在,则会优先使用它作为CDN的缓存时间
- Public:表明响应可以被任何对象包括客户端、服务器缓存
- Private:只能是发起请求的浏览器进行缓存
- No-cache:仍然对资源使用缓存,每一次在使用缓存之前必须向服务器对缓存资源进行验证,确认数据是否和服务器保持一致,实际上是需要进行协商缓存来验证决定是否需要缓存,表示不适用cache-control的方式左前置验证,二是使用ETag或者Last-Modified字段来控制缓存。
- No-store:所有内容都不会被缓存,即不使用强制缓存也不使用协商缓存
- Must-revalidate:如果配置了max-age,如果超过max-age的时间,即需要对资源的有效性进行验证
补充一下~max-age=0和no-cache在浏览器反应上是没有差别的,两者的区别在于:
max-age=0在语义上被理解为:到期应该(should)重新验证
no-cache:必须(must)重新验证
协商缓存:
强制缓存失效后,浏览器需要携带缓存标识与服务器协商是否使用缓存,有两种情况:
- 命中协商缓存,返回304状态码:
- 缓存失效,返回200状态码:
协商缓存由响应头中的Last-Modified和ETag + 请求头中的If-Modified-Since和If-None-Match控制
优化点在于:响应体积上的节省,通过减少响应体体积来缩短网络传输时间。对比缓存在请求数上和没有缓存是一样的,但是如果是304返回的仅仅是一个状态码,没有实际的内容
Response:Last-Modified + Request:If-Modified-Since
浏览器第一次访问资源时,服务器在返回资源的同时,同时传回Last-Modified这个数据告诉浏览器资源最后一次被修改的时间,浏览器将这个Last-Modified值和资源存到缓存数据库中;
当下次浏览器再次请求相同的资源时会检测到有Last-Modified这个header,于是将这个Last-Modified值写入到If-Modified-Since中传回服务器,服务器通过对比Last-Modified和If-Modified-Since两个值是否一致,如果一致就返回空响应体和304状态码,表示资源未被修改,直接从缓存中拉取资源;如果两个值不一致说明服务器中该资源被更新,则返回200和新的资源
Last-Modified的弊端:只能以秒记,如果处理文件在不可感知的时间内完成服务器还是会以为资源命中;如果本地打开缓存文件即使不修改也会改变Last-Modified值。
Response:ETag + Request:If-None-Match
整个流程同(Response:Last-Modified + Request:If-Modified-Since)只不过ETag是文件的特殊标识,由服务器生成,只要资源有变化,ETag就会重新生成。
两组数据对比:
ETag的优先级高于Last-Modified,即服务器也会优先验证If-None-Match;
在性能上ETag是逊于Last-Modified的
补充一个 CDN缓存:
CDN(Content Delivery Network)内容分发网络
采取与强制缓存相似的机制,把CDN看成是浏览器,把源服务器看成是浏览器需要请求的服务器,此时,源服务器的max-age头决定了资源在CDN节点本地缓存的时间,如果s-maxage存在,则会优先使用s-maxage
二、实际场景应用缓存
-
不经常更新的资源:
一般会设置一个很大的值,例如:Cache-Control: max-age=3453600,表示在3453600ms内再次加载资源就会命中强缓存。
-
频繁变动的资源:
首先需要设置:Cache-Control: no-cache 使得浏览器每次都请求服务器,然后配合ETag和Last-Modified来验证资源是否有效。
参考文章: