当第一次访问网站的时候,电脑会把网站中加载的图片与数据下载到电脑上,当我们再次访问该网站的时候,网站就会直接加载出来而并不是重新下载。在前端性能优化的方式中,缓存占据一定的地位,使用好了缓存会对前端有很大帮助。
其实就是在本地开辟一个内存区,同时也开辟一个硬盘区作为数据传输的缓冲区,然后用这个缓存区暂时保存用户以前访问过的信息。
缓存过程:强缓存、协商缓存
缓存位置:Service Worker -> Memory Cache -> Disk Cache -> Push Cache
它是运行在浏览器背后的独立进程,一般用来实现缓存功能。使用 Service Worker 的话,传输协议必须是HTTPS 协议,因为这个进程涉及到请求拦截,所以必须使用 HTTPS 协议来保障其安全。它与其他缓存机制不同,它可以让我自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。
//1.注册一个ServiceWorker
(async function regist() {
try {
let registration = await navigator.serviceWorker.register('service-worker.js', {scope: './'});
//register的第一个参数是脚本文件的路径,第二个参数中的scope(暂时只有这个属性)是这个脚本影响的作用域范围,如果像上面这样写,那么在当前路径/子路径下的页面都会受到影响。如果需要判断一个页面是否受service worker控制,可以检测navigator.serviceWorker.controller这个属性是否为null或者一个service worker实例。
} catch (e) {
console.error(e);
}
})()
//2.绑定install事件,当事件触发时就可以缓存需要的文件
self.addEventListener('install', function(event) {
// Perform install steps
event.waitUntil(
new Promise((resolve, reject) => {
setTimeout(resolve, 1000);
})
);
});
//3.下次用户在请求的时候就可以通过请求拦截的方式查询是否有缓存,存在有缓存的话就直接读取缓存中的数据,否则就去请求数据。
当 Service Worker 没有命中缓存的话,它会按照缓存优先级依次查找是否存在 Memory Cache、disk Cache、Push Cache,但不管从何处取得的缓存,浏览器都会显示我们从 Service Worker 中获取的内容。
它是保存在内存中的缓存,主要包含的是当前页面中的静态资源(图片、样式、脚本),从内存中读取数据肯定快于硬盘,但它的持续时间不长,关闭这个tab标签页后缓存就失效了。
需要注意的是,内存缓存在缓存资源的时候并不关心返回资源的 HTTP Header 中 Cache-Control 是什么值,同时资源的匹配也并非对 URL 做匹配,当然也有可能对 Content-Type 或 CORS 等做出校验。
存储在硬盘中的缓存,读取速度较慢,也是常见的一种缓存方式,主要是根据 HTTP 请求头中的字段判断哪些资源需要缓存、哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。即使在跨站点的情况下,相同地址的资源一旦被缓存下来,就不会再次请求数据。
它是 HTTP2 里面的内容,如果以上三种缓存没有命中的话,它才会被使用。它只存在 Session 会话中,一旦会话结束就被释放,并且缓存的时间也很短暂,在谷歌浏览器中只有5分钟左右,同时它也并非严格执行 HTTP 头中的缓存指令。
浏览器中的缓存机制大多数都是 HTTP 协议定义的缓存机制,但也有非 HTTP 定义的缓存机制,如使用 HTML meta 标签。
//cache模式,是否从缓存中访问网页内容,no-cache 禁止浏览器从本地计算机的缓存中访问页面
//网页到期时间,告诉浏览器不缓存页面每次都去服务器拉取
使用上比较简单,但只有部分浏览器支持,而且所有缓存代理服务器都不支持,主要是因为代理并不解析 HTML 本身,广泛应用的还是 HTTP 头信息来控制缓存
通过 HTTP 协议头里的 cache-control(或 expires)和 last-modified(或 etag)等字段来控制文件缓存的机制。这应该是 Web 中最早的缓存机制了,是在 HTTP 协议中实现的,有点不同于 Dom Storage、AppCache 等缓存机制,但本质上是一样的。可以理解为一个是协议层实现的,一个是应用层实现的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oWik6beH-1663316047946)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/040dfc8eb7c14ba09621da850b17d4f7~tplv-k3u1fbpfcp-zoom-1.image)]
当我们访问 url 的时候,不会向服务器发送请求,直接从缓存中读取资源,但是会返回 200 的状态码,并且 Size 显示 from disk cache 或 from memory cache。可以通过两种 HTTP Header 实现:expires 和 cache-control
它是 HTTP1.0的产物,表示缓存过期时间,值为一个时间戳,用来指定资源到期的时间,准确来讲是格林尼治时间(Thu Oct 16 07:13:48 GMT 2015)。时间是相对于服务器的时间而言的,但是如果客户端修改了本地时间导致与服务器时间不一致,可能会造成缓存失效。
针对“expires时间是相对服务器而言,无法保证和客户端时间统一”的问题,HTTP1.1增加了 cache-control 来来定义缓存过期时间。若同时出现了 expires 和 cache-control,则以 cache-control 为准。 它可以用在请求头或响应头中,并且可以组合多种指令:
它同样是 HTTP1.0 的产物,只有一个值 no-cache 也就是不适用强缓存,走协商缓存,该字段只适用于请求头,甚至可以乎忽略该字段的存在。
强缓存判断是否缓存的依据来自于是否超出了某个时间或时间段,而不关心服务器端文件是否已经进行更新,这可能导致加载文件不是服务器端的新内容。
强缓存失效后,浏览器携带缓存标识向服务器发起请求,服务器根据缓存标识来决定是否使用缓存的过程。主要解决强缓存下资源不更新的问题,当协商缓存生效时存在 304 Not Modified;当协商缓存失效时存在 200 请求结果。通过设置两种 HTTP Header实现:last-modified 和 etag
浏览器在第一次访问资源,服务器在返回资源的同时,在 Response Headers 中添加 last-modified,它的值为这个资源在服务器上的最后修改时间,浏览器接收到缓存文件和 Header。下一次请求这个资源时,检测到有last-modified,就会添加 if-modified-since 值就是 last-modified 中的值;服务器再次收到这个资源请求,会根据 if-modified-since 中的值与浏览器中这个资源的最后修改时间比,如果没有变化则会返回 304和 空的响应体,直接从缓存中读取;如果 if-modified-since 的时间小于服务器中这个资源的最后修改时间,说明文件有更新,于是返回 200 和新的资源.
弊端:
为了解决上述问题,HTTP1.1增加了 etag。etag 是服务端响应请求时,返回当前资源文件的唯一标识(由服务器通过 Hash 运算生成,类似文件指纹),只要资源发生了变化,etag 就会重新生成。浏览器下次加载资源请求服务器时,会将上一次返回的 etag 值放到请求头的 if-none-match 中,服务器只需要比较客户端传来的 if-none-match 的值与服务器上该资源的 etag 是否一致,就能判断该资源的文件是否进行修改。若一致,则直接返回304通知客户端直接使用本地缓存即可;若不一致,则会以常规 200 的形式将新的资源发给客户端。
强缓存优先于协议缓存,若强缓存(expires 和 cache-control)生效则直接使用缓存,若不生效则使用协商缓存(last-modified/if-modified-since 和 etag/if-none-match),协商缓存由服务器决定是否使用,若协商缓存失效,那么代表该请求的缓存失效,返回 200,重新返回资源和缓存标识,再存入浏览器中;生效则返回304,继续使用缓存。
HTTP 的缓存技术主要是为了提升网站的性能,如果不考虑客户端缓存容量和服务器计算能力的理想情况,当然希望客户端浏览器上的缓存触发率尽可能高,留存时间尽可能长,同时还要ETag实现当资源更新时进行高效的重新验证。但实际情况往往是容量和计算能力都有限,因此就需要指定合适的缓存策略,利用有效的资源达到最优的性能效果。既希望缓存能在客户端尽可能长久的保存,又希望他能在资源发生修改时进行及时更新。这时我们可以将网站所需要的资源按照不同的类型去拆解,为不同类型的资源制定相应的缓存策略。
cache-control:no-cache
cache-control: max-age=86400(一天)
cache-control: max-age=315360000(一年)
cache-control: private, max-age=315360000
缓存策略就是为不同的资源进行组合使用强制缓存,协商缓存及文件指纹或版本号,做到一举多得,及时修改更新,合理使用缓存过期时间及控制所能进行缓存的位置。
缓存设置需要注意不存在适用于所有场景下的最佳缓存策略,凡是恰当的缓存策略都需要根据具体的场景考虑制定。缓存决策要考虑下面几种情况。
对于大型项目来说,代码里是非常庞大的,如果发生修改的部分集中在几个重要的模块中,那么进行全量的代码更新显然比较冗杂,因此可以考虑在代码构建过程中按照模块拆分将其打包成多个单独的文件。这样在每次修改后更新提取时,仅需拉取发生改变的模块代码包,从而大大降低了需要下载的内容大小。
根据不同资源的不同需求特点来规划响应的缓存更新失效,为强制缓存指定合适的 max-age,为协商缓存提供验证更新的ETag实体标签。
凡是涉及用户隐私信息的尽量避免中间代理的缓存,如果对所有用户响应相同的资源,则可以考虑让中间代理也进行缓存。
缓存是根据请求资源的URL进行的,不同的资源会有不同的URL,所以尽量不要将相同的资源设置为不同的URL。这会导致缓存失效。
不仅请求的资源类型,文件资源的层次结构也会对制定缓存策略有一定的影响。