所谓机制,就是说有这么一套规则来束约缓存的逻辑. 束约的工具是浏览器内部实现的. 束约的参数就是我们 HTTP和HTTPS协议的报文内容. 结果就是用户看到的资源.
所以说我们在讨论的浏览器缓存机制的时候,就是在讨论HTTP报文中的某些对应字段的发挥的作用, 辅以一套处理这些字段的逻辑.
这套处理的逻辑可以分为两种强制缓存和协商缓存
强制缓存
在http1.0时代,通过配置expires相应头的属性,设置过期的时间,只要超过这个时间,资源就从服务器上获取.反之就从本地获取.
很简单,但是问题也很多
对本地时间戳过分的依赖,如果客户端本地的时间和服务器的时间不一致的话,那么缓存过期的判断就无法和预期相符
浏览器中的这个时间叫做格林威治时间
所以为了解决这个问题. 在http1.1时代,出现了cache-control字段. 其中的max-age属性就是对于expires的补充.
不是进行替代,而是补充.expires因为简单依旧存在它的使用场景
如下配置, 单位是秒:
res.writeHead(200m {
"Cache-control": "max-age:5"
})
这个max-age的含义超过相对时间. 每一次刷新都更新初始化时间,类似防抖函数的作用,当你渲染界面之后的 5 秒钟内,都是可以从缓存中拿到数据的。一旦渲染界面之后,再超出 5 秒钟才再点击拿资源的话,就会重新从服务器上面拿该资源。
很多时候还是需要加上public属性的. "public, max-age:5"的. 含义是: 响应可以被任何对象缓存(包括发送请求的客户端、代理服务器等)
如果只是使用max-age依旧是存在问题的.如果你后台的接口,资源就是在配置的的几秒钟更新了你怎么办?GG了. 所以出现了下面的协商缓存
协商缓存
协商缓存就要求每次都向服务器要结果. 缓存的有效性决定权交给后台.这样自然缓存存在的意义就很大的问题了.对于这个问题暂时不表,先来看看它是如果进行协商缓存的逻辑处理的.
last-modified 实现的协商缓存
这是最简单的协商缓存的方案, 根据文件的修改时间来进行判断. 如下配置
res.setHeader('last-modified', mtime.toUTCString())
res.setHeader('Cache-Control', 'no-cache')
配置成功了之后, 响应头会生成一个属性 if-modified-since. 然后后台再进行如下的判断:
const ifModifiedSince = req.headers["if-modified-since"];
if (ifModifiedSince === mtime) {
// 缓存生效
res.statusCode = 304;
res.end();
return;
}
如此一来,是能够满足绝大多是的场景的. 但是还是有如下的不足:
- 它只是根据时间戳来进行判断,如果只是改变了文件名,而实际内容没有任何改变的情况下,还是会进行服务器的请求拿取.这实在是太蠢了.
- 它的单位是秒.如果修改文件的速度非常快,在一些自动化文件处理中.在几百毫秒就完成了.那么它的单位就没有办法通过验证了.
所以为了解决这两点问题, HTTP1.1在随后更新版本中提供etag响应头字段来处理
ETag实现协商缓存
处理逻辑和last-modified基本一致
s const etag = require("etag");
const data = fs.readFileSync("./img4.png");
const etagContent = etag(data);
const ifNoneMatch = req.headers["if-none-match"];
if (ifNoneMatch === etagContent) {
// 缓存生效
res.statusCode = 304;
res.end();
return;
}
res.setHeader("etag", etagContent);
res.setHeader("Cache-Control", "no-cache");
res.end(data);
etag 表示的是对文件内容的解析进而生成的一个 id,只要文件内容有了改变才会进行变更。自然就能够改变last-modified的两点的不足。它是对其的一个补充方案,而不是替代方案。
etag 依旧带来了新的问题:
- 服务器生成文件资源 Etag 需要付出额外的计算开销,如果资源尺寸比较大,数量较多且修改比较频繁的话,那么生成 Etag 的过程显然会印象服务器的性能。
- Etag 字段值的生成两种类型,一种是强验证,即更具资源内容的每一个字节来进行验证,最可靠,性能消耗也最大。相对应的就是弱验证,它使用资源内容的部分的属性值来进行生成,生成速度快,但是没有办法很高的成功率。尤其是在服务器集群场景下。
所以说不管哪种缓存方式都有不足,结合具体的场景使用才是正确对待它们的方式. 一般来说,etag和last-modified都是使用的. 所以说对于它们的使用还有一个优先级的问题.
ETag和Last-Modified 的优先级
一般来说,默认配置的话. 是先进行etag的判断的,如果返回的是true的话,再判断last-modified.
当然这个可以后台自己实现自己喜欢的策略.
协商缓存过程的简单总结
可以总结如下图
其他相关配置的豹纹字段
Paragma: no-cache(响应头) HTTP/1.0版本的字段
Cache-Control: 也是操作缓存的.是HTTP/1.1版本字段,向下兼容的,所以说Paragma还是存在的.
Cache-Control的优先级是比前者高的.
Expires: Mon, 15 Aug2016 03:56:47 GMT(格林威治时间)
在HTTP/1.1使用Cache-control中的max-age来代替
Cache-Control的相关属性
-
no-cache: 忽略缓存在本地的副本,强制从服务器上拿资源
-
no-store: 强制缓存在任何情况下都不要保留任何副本
-
max-age=314600: 知识缓存副本的有效时长,从请求时间开始到过期时间之间的描述
-
public: 表明响应可以被任何对象缓存(包括:发送请求的客户端、代理服务器等)
-
private: 表明响应只能被耽搁用户缓存,不能作为共享缓存(即代理服务器不能缓存它)
我们前端需要做些什么
这里是熟悉前端工程话的知识点了.很多 Webpack基础已经帮我们做了. 我们只需要进行进行对应的配置就可以了. 比如说,修改每次打包都修改生成的入口文件的文字
entry:{
main: path.join(__dirname,'./main.js'),
vendor: ['react']
},
output:{
path:path.join(__dirname,'./dist'),
publicPath: '/dist/',
filname: 'bundle.[chunkhash].js'
}
chunkhash就代表出口文件没有打包都会生层对应的hash值. 还有另外两个值可以替换它.hash、contenthash
三者的差别可以用一句话来概括:
hash计算和整个项目的构建相关chunkhash计算同一chunk内容相关contenthash计算和文件内容本身相关 详情可以自己尝试一下.看官网.
还有一些前端世界常听到的:
- html使用协商缓存
- css、js、静态资源 使用强缓存,文件名带上hash值