浏览器/缓存知识

200 阅读3分钟

1.按缓存位置分类

按缓存位置分类,可以分为:Memory Cache(内存缓存),Disk Cache(磁盘缓存)

1.1Memory Cache(内存缓存)

memory cache 叫内存缓存,内存缓存具有两个特点,分别是快速读取和时效性:

  • 快速读取:内存缓存会将编译解析后的文件,直接存入该进程的内存中,占据该进程一定的内存资源,以方便下次运行使用时的快速读取
  • 时效性:一旦该进程关闭(网页关闭),则该进程的内存则会清空。

在浏览器中,浏览器会在js和图片等文件解析执行后直接存入内存缓存中,那么当刷新页面时只需直接从内存缓存中读取(from memory cache);当然内存缓存是有存储限制的,一旦超过限制,则超过的数据会从磁盘缓存获取。

1.2Disk Cache (磁盘缓存)

disk cache 也叫 磁盘缓存。它允许相同的资源在跨会话,甚至跨站点的情况下使用,例如两个站点都使用了同一张图片。

该机制的作用是:当一个页面打开发送请求时,(磁盘缓存) 会严格根据 HTTP 头部中的各类字段来进行缓存。当命中缓存之后,浏览器会从硬盘中读取资源,虽然比起从内存中读取慢了一些,但比起网络请求还是快了不少的。

凡是持久性存储都会面临容量增长的问题,disk cache 也不例外。浏览器会根据自身算法自动清理“最老的”或者“最可能过时的”资源。

1.3查找资源的优先级

memory cache(内存缓存) ---------> disk cache(磁盘缓存) ---------> 发送请求

1.5如何查看数据是否来自缓存?

使用google浏览器

内存缓存

image.png

磁盘缓存

这是强缓存

这是协商缓存,状态码为304 image.png

2.http缓存

浏览器有个缓存数据库,当请求的数据被缓存过,那么请求的数据直接从缓存数据库拿取。

缓存类型分为强缓存协商缓存

2.1强缓存

浏览器第一次请求的数据被服务器设置成有强缓存时间的数据,那么浏览器第二次请求的时候,如果时间未过期,那么请求将不会发送到服务器,数据直接从浏览器缓存数据库获取。(从google浏览器可以看到是from disk cache)

2.1.1设置的方法:

响应头中设置Expires

Expires: Thu, 10 Nov 2017 08:45:11 GMT // gmt 绝对时间

与响应头中的date时间进行对比,如果Expires(本地浏览器时间) < date(服务器时间),则过期。
未过期则不需要发送请求。
但是本地浏览器的时间与服务器的时间有很大的误差,通常不准。

响应头中设置 Cache-control

Cache-Control: max-age=2592000

与响应头中的date时间进行对比,在datemax-age秒后,强缓存将失效。
但在该时间内,客户端不需要向服务器发送请求。

Cache-Control: no-cache

可以在本地和代理服务器缓存,但是这个缓存需要服务器验证才可以使用,即直接进入协商缓存阶段。

Cache-Control: no-store

真正意义上的“不要缓存”,不进行任何形式的缓存,每次都从服务器获取

Cache-Control: private

客户端可以缓存,代理服务器不能缓存。例如,用户的浏览器可以缓存包含用户私人信息的HTML网页,但CDN不能缓存

Cache-Control: public

客户端和代理服务器都可以缓存

Cache-Control: must-revalidate

must-revalidate告诉缓存,在事先没有跟原始服务器进行再验证的情况下,不能提供这个对象的陈旧副本,
缓存仍然可以随意提供新鲜的副本。
如果在缓存进行must-revalidate新鲜度检查时,原始服务器不可用,缓存就必须返回一条504错误。

Cache-Control: s-maxage

s-maxage是针对代理服务器的缓存时间

同时设置cache-control 跟 Expires

如果 cache-control 跟 Expires 同时存在,Expires 会被 cache-control 覆盖 。 http1.1已经把它抛弃了,基本没用了。

2.1.2实践代码

const express = require('express');
const app = express();
const port = 8080;
const fs = require('fs');
const path = require('path');
const moment = require('moment');
app.get('/',(req,res) => {res.send(`<!DOCTYPE html><html lang="en"><head><title>Document</title></head><body>Http Cache Demo<input type='button' onclick='getData()' value='获取数据'><script src="/demo.js"></script><script >function getData(){const xhr = new XMLHttpRequest()xhr.onreadystatechange = () => {if (xhr.readystate == 4) {if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {alert(xhr.responseText)} else {alert("Request was unsuccessful: " + xhr.status)}}}xhr.open("get", "/demo1.js", true)xhr.send(null)}</script></body></html>`)})function getGLNZ(){  return moment().utc().add(2,'m').format('ddd, DD MMM YYYY HH:mm:ss')+' GMT';}
app.get('/demo1.js',(req, res)=>{  let jsPath = path.resolve(__dirname,'./static/js/demo1.js');  let cont = fs.readFileSync(jsPath);  res.setHeader('Cache-Control', 'public')  res.setHeader('Cache-Control', 'max-age=31536000')  console.log(123)  res.end(cont)})
app.listen(port,()=>{  console.log(`listen on ${port}`)})

结果:

2.2协商缓存

网页发送资源请求时,发现强缓存的时间过期了,但服务器的资源并没有发生改变,此时服务器会下发状态码为304的响应请求,告诉浏览器还是从磁盘缓存中获取数据,这就是协商缓存。

2.2.1强缓存与协商缓存的工作流程

image.png

强缓存的时间过期, 该请求会从缓存的请求里面找到Etag的值,如果Etag的值存在,则将该值赋给请求头的If-None-Match。当请求发送到服务器时,服务器会取出If-None-Match的值,并与服务器文件的Etag进行比较,如果一致,则响应304状态码,告诉浏览器继续使用缓存。如果不一致,则返回200状态码,并更新Etag的值。

如果Etag的值不存在,则从缓存的请求里面找到Last-Modified的值,则将该值赋给请求头的If-Modifed-Since,当请求发送到服务器时,服务器会取出If-Modifed-Since的值并于服务器文件的最后修改时间进行比对,如果一致,则响应304状态码,告诉浏览器继续使用缓存。如果不一致,则返回200状态码,并更新Last-Modified的值。

2.2.2实际代码

const express = require('express');
const app = express();
const port = 8080;
const fs = require('fs');
const path = require('path');
const moment = require('moment');
app.get('/',(req,res) => {res.send(`<!DOCTYPE html><html lang="en"><head><title>Document</title></head><body>Http Cache Demo<input type='button' onclick='getData()' value='获取数据'><script src="/demo.js"></script><script >function getData(){const xhr = new XMLHttpRequest()xhr.onreadystatechange = () => {if (xhr.readystate == 4) {if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {alert(xhr.responseText)} else {alert("Request was unsuccessful: " + xhr.status)}}}xhr.open("get", "/demo2.js", true)xhr.send(null)}</script></body></html>`)})function getGLNZ(){return moment().utc().add(2,'m').format('ddd, DD MMM YYYY HH:mm:ss')+' GMT';}app.get('/demo2.js',(req, res)=>{  let jsPath = path.resolve(__dirname,'./static/js/demo1.js');  
let cont = fs.readFileSync(jsPath);  
let status = fs.statSync(jsPath)  
let lastModified = status.mtime.toUTCString()  
if(lastModified === req.headers['if-modified-since']){   res.writeHead(304, 'Not Modified')   res.end()  } 
else {  res.setHeader('Cache-Control', 'public')  res.setHeader('Cache-Control', 'max-age=5')  res.setHeader('Last-Modified', lastModified)  res.writeHead(200, 'OK')  res.end(cont)}})
app.listen(port,()=>{console.log(`listen on ${port}`)})

3.更新和废弃缓存的方法

将资源文件更改成hash名称,迫使请求路径发生变化,从而开始下载新的资源。

4.缓存应用

不常变化的资源

Cache-Control: max-age=31536000

经常变化的资源

Cache-Control: no-cache

5.参考资料来源

juejin.im/post/684490… blog.csdn.net/guduyibeizi…