浏览器缓存

233 阅读8分钟

公司每周五的分享开始拉,本次分享的主题是前端缓存,分享者就是我的menter宇哥啦。这次分享对前端缓存也了解的更多了,比如200和304到底什么时候返回?是只有向服务端请求成功才会返回200吗?到底有哪些东西能够控制缓存,又是根据什么来判断使用缓存还是重新加载服务器资源?这些本篇文章都会讲到。

目录

  • 缓存分类
  • 缓存控制
  • 缓存更新机制
  • 缓存最佳实践
  • 问题环节

缓存分类

  1. 私有浏览器缓存

    只能作用于当前客户端,即浏览器将资源存储在本地

  2. 共享代理缓存

    代理缓存,多个用户都可以用该缓存,即缓存服务器,例如CDN。

缓存控制

http头信息字段 值类型 介绍 http版本
cache-control no-cache
no-store
max-age
private
pubic
每次需要向服务端确认缓存是否失效
直接禁用缓存
资源缓存的时间长度(单位为秒)
只能使用浏览器的缓存,不能使用CDN等代理缓存
资源可以被代理服务器缓存
HTTP/1.1
expires 一个时间点 资源过期的一个时间点,存在的问题是客户端时间不一定和服务器的一致,所以使用他控制缓存会存在问题,所以在HTTP/1.1中出现了max-age HTTP/1.1
Pragma no-cache 和cache-control中设置no-cache实现的功能是一样的,但是如果两者同时出现,会以Pragma为准,这也是为了兼容HTTP/1.0 HTTP/1.0

缓存控制基本上就是靠上面的这几个http头字段来控制缓存,其中使用最多的就是cache-control了,最好是cache-control和expires同时设置,这样当某一个头没设置的时候,另一个也能实现缓存。

缓存更新机制

缓存更新相关又涉及到了其他的http头信息,先了解一下相关的头信息,然后再去了解缓存更新的机制

http头信息字段 值类型 介绍
ETag 服务端生成,通常是使用最近修改的时间戳的哈希值或者使用抗碰撞散列函数生成 等价于一个资源的标志符,如果这个资源被修改来,则生成一个新的标志符,和之前的进行对比就知道资源是否被修改了
Last-Modified 时间点 表示资源最后修改的时间点
date 时间点 请求发送的日期和时间
If-Match <etag_value> etag的值,可以为多个etag值,用","隔开,如果请求的资源的etag值在If-Match中存在的话,那么才会发起请求
If-None-Match <etag_value> 如果请求的资源的etag值在If-None-Match中找不到,则服务器会返回请求的资源并且状态码为200
f-Modified-Since 时间点 服务器只在所请求的资源在给定的日期时间之后对内容进行过修改的情况下才会将资源返回,状态码为 200 。如果请求的资源从那时起未经修改,那么返回一个不带有消息主体的 304 响应
If-Unmodified-Since 时间点 只有当资源在指定的时间之后没有进行过修改的情况下,服务器才会返回请求的资源

有了上面的这些字段,接下来就可以看一看到底是怎样判断使用缓存和怎样进行缓存更新。首先我们看一张图

  1. 第一次请求由于没有缓存,所以直接发送到服务器获取资源,然后服务器返回资源后,缓存就存在了。并且服务端设置了缓存的时间为100s
  2. 第二次请求是在10s之后,先进缓存,缓存发现才过了10s,cache-control中的max-age是100,明显还没到时间,所以缓存肯定是可用的,于是不去请求服务器了,直接从缓存读取资源并且返回200状态码
  3. 第三次过了100s后,先过缓存,发现时间过了max-age设置的100s,所以需要向服务器发送请求,判断这个缓存是否还能用,这时候缓存会给请求新增If-None-Match字段,服务器根据这个字段来判断服务器端资源的etag是否在If-None-Match字段中,如果匹配不到,说明资源更新了,于是服务端返回带资源的实体,并且返回200状态码,如果匹配到了,说明资源没变,返回304状态吗并且不带资源实体。

对于含有特定头信息的请求,会去计算缓存寿命。比如Cache-control: max-age=N的头,相应的缓存的寿命就是N。通常情况下,对于不含这个属性的请求则会去查看是否包含Expires属性,通过比较Expires的值和头里面Date属性的值来判断是否缓存还有效。如果max-age和expires属性都没有,则找头里的Last-Modified信息。如果有,缓存的寿命就等于头里面Date的值减去Last-Modified的值除以10(注:根据rfc2626其实也就是乘以10%)。

缓存最佳实践

  1. html页面设置:no-cache,每次请求页面都会让服务器判断该页面是否修改了
  2. /script.82d3ff.js:cache-control: max-age=31536000, private
  3. img:cache-control: max-age=864000
  4. /style.34f3ac.css:cache-control: max-age=31536000

为什么把html设置成no-cache呢?因为资源都是通过html页面进行加载的,如果资源修改了,只需要修改html页面中资源的名称就可以,这样缓存自动会缓存新的资源,自然就更新了。这样也不用每次判断js、css是否是最新的。之所以js设置了private,是为了一些安全性考虑所以只让本地缓存。图片名称一般不会变化,所以我们把图片的缓存时间设置短一些。现在的打包工具都会给打包后的资源加上一个标示,所以更新文件打包后,我们也不需要手动修改文件名。非常方便。

相信大家都用过express,express中设置静态文件使用的是express.static 来设置静态文件夹,但是这样是针对所有的静态文件设置的都是同样的头信息,我们想读取html时设置cache-controlno-cache 其他类型的文件不设置no-cache 该怎么配置呢?

var express = require('express');
var path = require('path');
var ejs = require('ejs');
var app = express();
var serveStatic = require('serve-static')
// 管理静态文件
app.use(serveStatic(path.join(__dirname, 'public'), {
  setHeaders: setCustomCacheControl
}))

function setCustomCacheControl (res, path) {
  switch(serveStatic.mime.lookup(path)) {
    case 'text/html': res.setHeader('Cache-Control', 'public, no-cache'); break;
    case 'application/javascript': res.setHeader('Cache-Control', 'private, max-age=31536000'); break;
    case 'image/jpeg': res.setHeader('Cache-control', 'max-age=864000'); break;
    case 'text/css': res.setHeader('Cache-control', 'max-age:31536000'); break;
  }
}
// 设置模板引擎页面
app.set('views', './views');
app.set('view engine', 'ejs');
app.get('/', function (req, res) {
  res.render('index', { title: '我是标题'});
});

通过判断不同的数据类型来设置不同的头,这样就能达到效果啦。有兴趣的可以弄个小demo试一试。

问题环节

  1. 什么时候返回200什么时候304?

    200: 1. 请求成功返回200; 2. 缓存未过期,并且没有设置no-cache时,返回200 304: 1. 缓存时间未到,设置了no-cache时每次向服务器确认,如果资源没有被修改,则返回304;2. 缓存时间到了,向服务器确认,资源没被修改,则返回304

  2. 缓存过期了就不会使用缓存了吗?

    不一定,缓存过期了但是资源不一定变化了,如果服务器短确认了资源未变化,那么依然会使用缓存。

  3. 控制缓存的头信息有哪些?
    1. cache-control; 2. expires; 3.Pragma
  4. 使用缓存的判断流程?

  1. etag和last-Modified有什么区别?

从第四问中的流程图上其中可以看到, etag和if-None-Match是相对应的,Last-Modified和If-Modified-Since是对应的,他们两个的功能其实是一样的,都是交给服务器去判断资源是否更新,但是etag的优先级更高,而且更精准。why?因为我们知道last-Modified保存的是一个时间点,并且他只能精确到秒级,如果在一秒内资源被修改了多次,那么使用last-Modified是判断不出资源更新的,而etag是服务器端生成的资源的唯一标志符,文件修改了该标志就变了,所以能够准确判断资源是否更新。

参考文章

浏览器 HTTP 协议缓存机制详解

HTTP 缓存

express serve-static

补充一点

  1. 上述的都是针对于静态资源来说的,如果是动态生成的html,例如返回的是ejs模板页面,那么上面的express代码中的text/html分支是不会走的,因为ejs模板就不是静态资源。
  2. 具体的express例子,可以参考我的express缓存示例github