你可能需要了解的浏览器缓存知识

浏览器缓存

缓存的原理是首次请求后存储一份请求资源的响应副本,当用户再次发起相同请求后,如果缓存命中,则直接将之前存储的副本返回给浏览器,从而避免重新向服务器发起资源请求。

image.png HTTP缓存是我们日常开发中最常用的一种缓存技术。根据判断缓存命中时,浏览器是否需要向服务器发起请求以询问缓存的相关信息来划分,HTTP缓存又可以分为强缓存和协商缓存。

强缓存

缓存策略都是通过设置HTTP Header来实现的,与强缓存相关的Header字段是 Expires 和 cache-control。

Expires是 HTTP1.0 协议中控制缓存失效日期时间戳的字段,是绝对时间,当客户端本地时间超过 Expires 的时间,表示缓存过期。

Expires: Mon, 21 Apr 2031 01:13:16 GMT
复制代码

Cache-Control是HTTP1.1协议中控制缓存的字段,Cache-Control 的 max-age 属性控制资源缓存的有效期,是一个以秒为单位的时间长度,如下,表示该缓存在资源被请求到后的315360000秒内有效。

Cache-Control: max-age=315360000
复制代码

Cache-Control 还可以配置一些其他属性值来控制缓存:

  • no-store:禁止使用任何缓存策略,每次请求都去服务器去获取最新资源。
  • no-cache:每次发起请求不会直接进入强缓存的过期校验,而是直接与服务器协商来验证本地缓存的有效性,也就是走下面要讲的协商缓存路线。
  • private:资源只能被浏览器缓存,Cache-Control的默认取值。
  • must-revalidate:可缓存但必须再向服务器进确认。

(以下是对代理服务器缓存的设置)

  • public:资源既可被浏览器缓存,又可被代理服务器缓存。
  • s-maxage: 缓存在代理服务器中的过期时长。
  • proxy-revalidate: 要求代理服务器对缓存的响应有效性再进行确认。

在HTTP1.1中,Cache-Control是Expires的完全替代方案,通常来说,在项目实践中使用它就足够了,继续使用 Expires 是为了兼容 HTTP1.0。

协商缓存

当 Cache-Control 校验缓存过期,或者设置了no-cache时,浏览器就会与服务器进行协商,判断是重新发起请求、下载完整的响应,还是从本地获取缓存的资源。

跟协商缓存相关的 Header 字段有Last-Modified / If-Modified-SinceETag / If-None-Match (分别成对出现)

Last-Modified / If-Modified-Since

在每次服务器返回资源的同时,会在响应头中同时返回Last-Modified字段,它是一个时间戳,表示资源最近一次的修改时间。

last-modified: Mon, 11 Jan 2021 08:15:02 GMT
复制代码

随后我们每次需要发送请求时,会带上一个叫 If-Modified-Since 的时间戳字段,它的值就是上一次 response 返回的 Last-Modified 值。

if-modified-since: Mon, 11 Jan 2021 08:15:02 GMT
复制代码

服务器接收到这个时间戳后,会比对该时间戳和资源在服务器上的最后修改时间:

  • 如果两者不同,返回200和完整的响应内容。
  • 如果两者相同,返回304和空的响应体,直接从缓存读取资源。

需要注意的是,强缓存命中对应的状态码是200,协商缓存命中对应的状态码是304。

ETag / If-None-Match

Etag 是由服务器为每个资源生成的唯一的标识字符串,类似于文件指纹。和 Last-Modified 类似,当首次请求时,会在响应头返回Etag字段。

etag: "88158AFCFF1E7F4B8B00A1BA81171B61"
复制代码

下一次请求时,会将之前响应头中Etag的值作为此次请求头中If-None-Match字段,提供给服务器进行校验。 若资源没有更新,就返回304和空的响应体,直接从缓存读取资源。否则,就会返回返回更新后的资源并且将新的缓存信息一起返回。

Etag 在感知文件变化上比 Last-Modified 更加准确,优先级也更高。当 Etag 和 Last-Modified 同时存在时,以 Etag 为准。

应用场景

html文档一般会设置 no-cache。在meta中设置:

<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="cache-Control" content="max-age=0">
复制代码

CSS,JS,图片等静态文件一般使用强缓存,设置较长的过期时间,并且文件名添加hash。比如掘金的文章详情页,js文件的缓存过期时间为max-age=31536000(一年)。

image.png

同时文件名必须添加hash,这样当文件名hash发生变化,则视为新的资源,从服务端直接获取。这样可以减少大量的静态资源的网络请求,由新hash标志的文件也不会因为缓存的原因滞后。

通过webpack,我们可以方便地为文件名添加hash。

用webpack为文件名添加hash

文件指纹是一串哈希符,通常是加载文件名后面,用于标识文件,做版本的管理。比如项目上线后,需要修改一些东西,只需要发布修改过的文件,对于没有修改过的文件,用户在访问的时候,依旧可以使用浏览器缓存,加速页面渲染。

webpack中的文件指纹有三种:

  • hash:和整个项目的构建相关,所有文件共用一个hash值,任何文件有修改,项目的hash值就会更改,没有实现缓存的效果。
  • chunkhash:根据不同的入口文件(entry)进行依赖文件解析、构建对应的chunk,生成对应的chunkhash值
  • contenthash: 由文件内容产生的hash值,内容不同产生的contenthash值也不一样。

设置文件指纹的通常是静态资源文件,以下是几种文件的文件指纹设置方法:

  • js:设置output的filename,使用[chunkhash]
output: {
   path: path.join(__dirname,"/dist"),
   filename: "js/bundle.[chunkhash:6].js"
}
复制代码
  • css:只有将css抽离成单独的文件时才需要设置指纹,借助mini-css-extract-plugin将css提取成独立的文件,设置mini-css-extract-plugin的filename。使用[contenthash],如果只修改js就可以不用重新上传css文件。
new MiniCssExtractPlugin({
    filename: 'css/[name].[contenthash:7].css'
})
复制代码
  • 图片:设置file-loader或url-loader的name,使用[hash],与上面整个项目构建的hash不同,图片等媒体文件的hash是根据文件的内容所生成的,换成[contenthash]也可以。
{
    test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
    loader: 'file-loader', 
    options: {
    	esModule: false,
        name: 'img/[name].[hash:7].[ext]'
    }
}
复制代码

使用热更新的情况下,不能用chunkhash或contenthash,会报错,只能使用hash。所以在开发环境下一般不设置文件指纹,在生产环境下设置。

猜你喜欢

转载自juejin.im/post/7084864717147078663