type
Post
status
Published
date
Nov 12, 2022
slug
front-end-cache
summary
其实前端缓存理解起来很容易,虽然在日常开发中并不经常遇到缓存引起的问题,但是面试八股中出现频率还是比较高的。
tags
前端
开发
category
学习思考
icon
password
Property
Nov 13, 2022 07:16 AM

缓存

首先,什么是缓存?缓存就是一个资源副本。当我们想服务器请求资源后,就会根据情况将资源复制一份存储在本地,方便下次读取。它与本地存储localStoragecookie等不同,本地存储记录的更多是数据记录,存储量较小,为了本地操作方便。而缓存更多是为了减少资源请求,多用于存储文件,存储量相对较大。
那么缓存有什么用呢?缓存最根本的作用就是减少没有必要的请求。有些资源,例如用户头像图片,很久才改变一次,但每次都需要请求这张一样的图片,通信一来一回增加了页面的显示时长,过多没必要请求页增加了服务器的压力。如果把这张图片直接缓存在本地,那每次就可以直接本地读取加载,不再发起请求。所以缓存的好处也就显而易见了,减少了时长从而优化用户体验,同时也减少了流量消耗,减轻了服务器压力
那么缓存有哪些呢?就浏览器而言,一般缓存我们分为 4 类,按照浏览器读取优先级顺序依次为 Memory Cache、Service Worker Cache、Http Cache、Push Cache。而本篇文章主要讲的就是 Http Cache。

HTTP Cache

HTTP Cache 是我们开发中接触最多的缓存,它分为强缓存和协商缓存。
  1. 强缓存:直接从本地副本对比读取,不去请求服务器,返回的状态码是 200。
  1. 协商缓存:会去服务器对比,若没有改变才直接读取本地缓存,返回的状态码是 304。

强缓存

强缓存包括expirescache-control

expires

expires是 HTTP/1.0 协议中定义的用于缓存的字段。当我们请求一个资源,服务器返回时,可以在Response Headers里面增加expires字段表示资源过期时间。
expires: Thu, 03 Jan 2022 11:43:04 GMT
它是一个时间戳(格林尼治时间),当客户端再次请求该资源的时候,会把客户端时间与该时间戳进行对比,如果大于该时间戳则已过期,否则直接使用该缓存资源。
但是有个大问题,发送请求时使用的是客户端时间去对比。一是客户端和服务器时间可能快慢不一致,另一方面可能客户端时间是自行修改的(例如浏览器是跟随系统时间的,修改系统时间会影响到),所以不一定满足预期。

cache-control

正由于上面所说的可能存在的问题,HTTP1.1新增了cache-control字段来解决该问题,所以当cache-controlexpires都存在时,cache-control的优先级更高。改字段是一个时间长度,单位是秒,表示该资源过了多少秒之后失效。当客户端请求资源的时候,发现该资源还在有效时间内则使用缓存,它不依赖客户端时间cache-control主要有max-ages-maxagepublicno-cacheno-store等值。
cache-control: public, max-age=3600, s-maxage=3600
  • max-ages-maxage:这两者是cache-control的主要字段,它们是一个数字,表示资源过了多少秒之后变为无效。在浏览器中,max-ages-maxage都起作用,而且s-maxage的优先级高于max-age。在代理服务器中,只有s-maxage起作用。可以通过设置max-age为 0 表示立马过期来向服务器请求资源。
  • publicprivatepublic表示资源可以被所有客户端和代理服务器缓存,而private表示该资源仅能客户端缓存。默认值是private,当设置了s-maxage的时候表示允许代理服务器缓存,相当于public
  • no-cacheno-storeno-cache表示的是不能直接询问浏览器缓存情况,而是去向服务器验证当前资源是否更新(即协商缓存)。no-store则更狠,完全不使用缓存策略,不缓存请求或响应的任何内容,直接向服务器请求最新。由于两者都不是直接与服务器交互,所以当no-cacheno-store存在时会直接忽略max-age等。

pragma

pragma的值有no-cacheno-store,表示意思同cache-control,优先级高于cache-controlexpires,即三者同时出现时,先看pragma,然后cache-control,然后expires

协商缓存

上面的expirescache-control都会访问本地缓存直接验证看资源是否过期,如果没过期则直接使用本地缓存,并返回 200。但如果设置了no-cacheno-store,则本地缓存会被忽略,回去请求服务器验证资源是否更新,如果没更新才继续使用本地缓存,此时返回的是 304,这就是协商缓存。协商缓存主要包括last-modifiedetag

last-modified

last-modified记录资源最后修改的时间,启用后,请求资源之后的响应头会增加一个last-modified的字段。
last-modified: Thu, 20 Dec 2018 11:36:00 GMT
当再次请求该资源时,请求头中会带有if-modified-since字段,值是之前返回的last-modified的值。服务器会对比该字段和资源的最后修改时间,若一致则证明没有被修改,告知浏览器可直接使用缓存并返回 304;若不一致则直接返回修改后的资源,并修改last-modified为新的值。
last-modified有以下两个缺点:
  • 只要编辑了,不管内容是否真的有改变,都会以这最后修改的时间作为依据,当成新资源返回,从而导致了没有必要的请求响应,而这正是缓存本来的作用,即避免没必要的请求。
  • 时间的精确度只能到秒,如果在一秒内的修改是检测不到更新的,仍会告知浏览器使用旧的缓存。

etag

为了解决last-modified上述问题,有了etagetag会基于资源的内容编码生成一串唯一的标识字符串,只要内容不同,就会生成不同的etag。启用etag之后,请求资源后的响应返回会增加一个etag字段。如下:
etag: "FllOiaIvA1f-ftHGziLgMIMVkVw_"
当再次请求资源时,请求头会带有if-none-match字段,值是之前返回的etag的值。服务端会根据该资源当前内容生成对应的标识字段和该字段进行对比,若一致则代表未改变可直接使用本地缓存并返回 304;若不一致则返回新的资源(状态码 200)并修改返回的etag字段为新的值。
可以看出etaglast-modified更加精准地感知了变化,所以etag优先级也更高。不过从上面也可以看出etag存在的问题,就是每次生成标识字符串会增加服务器的开销。所以要如何使用last-modifiedetag还需要根据具体需求进行权衡。

访问刷新分析

我们将访问和刷新分为以下三种情况:
  • 标签进入、输入 url 回车进入
  • 按刷新按钮、F5 刷新、网页右键重新加载
  • Ctrl + F5 强制刷新
假设有一个页面,其返回的响应信息如下:
cache-control: max-age=72000 expires: Tue, 20 Nov 2018 20:41:14 GMT last-modified: Tue, 20 Nov 2018 00:41:14 GMT
第一种情况下,会根据实际设计的缓存策略去判断。由于该例没有设置no-cacheno-store,所以默认先走强缓存路线。根据cache-controlexpires优先级低)判断缓存是否过期,若没有过期则返回200(from cache)。如果本地缓存已经过期,那么再走协商缓存路线,根据之前的last-modified值去和服务器对比,若这个时间之后没有改过则去读取本地缓存,返回304(not modified)。否则返回新的资源,状态码200(ok),并更新返回响应的last-modified值。
再看看第二种情况。这种情况下,实际是浏览器将cache-controlmax-age直接设置为了 0,让缓存立即过期,直接走协商缓存路线。发送的请求头如下:
cache-control: max-age=0 if-modified-since: Tue, 20 Nov 2018 00:41:14 GMT
最后一种强制刷新的情况下,浏览器会强行设置no-cache,强制获取最新的资源,就连if-modified-since等其他缓存协议字段都会被吃掉,此时发送的请求头如下:
cache-control: no-cache pragma: no-cache
好剧推荐——C.S.I.犯罪现场调查使用ESLint、Prettier统一团队代码