前端性能优化之缓存


theme: condensed-night-purple
highlight: a11y-light

logo.jpg

大家好,我是侃如,最近在学习node.js,涉及到缓存,发现自己有很多不清晰的地方,在这里对涉及到的知识点简单做一下总结。

缓存

缓存是一种保存资源的技术,保存当前请求资源以便下次请求时可以直接使用,而不需要再次访问源服务器下载。
缓存的好处:缓解服务器压力,减少数据请求次数;提高资源加载速度,提升性能,降低网络负荷等;

缓存的种类:浏览器缓存、网关缓存、CDN、反向代理缓存等。

本文主要对浏览器缓存HTTP缓存做一下总结。

浏览器缓存和HTTP缓存的区别

之前有同事将浏览器缓存和HTTP缓存视为同一概念,也可以,但感觉不是很合理,在这里简单说一下他们的区别:

HTTP缓存:在HTTP请求传输时用到的缓存,通常是在服务端设置的,由缓存规则决定缓存方式;

浏览器缓存:使用js设置,主要涉及本地资源的存储方式。

HTTP缓存

HTTP缓存一般应用于get请求,主要分为强缓存(强制缓存)和协商缓存
可以对照这张流程图理解。

cache.jpg

强缓存

顾名思义,强制进行缓存,当客户端去请求某个资源文件时,服务端返回资源文件的同时还会携带一个响应头(Expires/Cache-Control)字段,这个字段就表示缓存的规则,当Expires和Cache-Control同时存在时,Cache-Control优先级更高

Expires:用一个格林尼治时间表示缓存的过期时间,浏览器请求时如超过该时间,表示过期,需要重新请求服务器;如未超过该时间,直接使用缓存。

Expires使用客户端的时间与服务端的时间做对比,它有很大的局限性,如果两种时间有误差,那么强缓存直接失效,因此,在HTTP1.1(当前浏览器默认版本) Expires被Cache-Control取代。

Cache-Control:针对Expires的问题,Cache-Control引入了新的更强大、更灵活的缓存规则。Cache-Control不仅可以在服务端响应中使用,还可以在客户端 HTTP 请求中使用。

Cache-Control客户端参数

Cache-Control: max-age=<seconds> //设置缓存存储的最大时间,超过这个时间缓存被认为过期
Cache-Control: max-stale[=<seconds>] //表明客户端愿意接收一个已经过期的资源
Cache-Control: min-fresh=<seconds> //表示客户端希望获取一个能在指定的秒数内保持其最新状态的响应。
Cache-control: no-cache //强制要求缓存把请求提交给协商缓存进行验证
Cache-control: no-store //不使用任何缓存
Cache-control: no-transform //不得对资源进行转换或转变。Content-Encoding、Content-Range、Content-Type等 HTTP 头不能由代理修改。
Cache-control: only-if-cached //表明客户端只接受已缓存的响应

Cache-Control服务端参数:

Cache-control: must-revalidate  //一旦资源过期(比如已经超过max-age),在成功向原始服务器验证之前,缓存不能用该资源响应后续请求。
Cache-control: no-cache //强制要求缓存把请求提交给协商缓存进行验证
Cache-control: no-store //不使用任何缓存
Cache-control: no-transform //不得对资源进行转换或转变。Content-Encoding、Content-Range、Content-Type等 HTTP 头不能由代理修改。
Cache-control: public //共享缓存,表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存,即使是通常不可缓存的内容。
Cache-control: private //私有缓存,表明响应只能被单个用户缓存(本地浏览器),不能作为共享缓存(即代理服务器不能缓存它)
Cache-control: proxy-revalidate  //与 must-revalidate 作用相同,但它仅适用于共享缓存
Cache-Control: max-age=<seconds> //设置缓存存储的最大周期,超过这个时间缓存被认为过期 (单位秒)
Cache-control: s-maxage=<seconds> //覆盖max-age,但是仅适用于共享缓存 (比如各个代理),私有缓存会忽略它。

简单使用express框架搭一个服务器,使用setHeader方法设置Cache-Control字段即可:

app.get('/index.html',(req, res)=>{
  let getPath = path.resolve(__dirname,'index.html');
  res.setHeader('Cache-Control', 'max-age=2') 
  res.end(fs.readFileSync(getPath))
})

adbe888c6c22a419b3e4f2e492f6329.png

协商缓存

在强缓存失效(过期等原因,不是不存在)后,客户端会携带缓存标识向服务端发起请求,服务端会根据缓存标识决定是否使用缓存。

协商缓存的标识字段有两组,Last-Modified / If-Modified-SinceEtag / If-None-Match,其中Etag / If-None-Match的优先级比Last-Modified / If-Modified-Since。和强缓存一样,协商缓存的标识也是在HTTP的响应头中返回给浏览器的。

在协商缓存生效时,状态码为304,并且请求时间和资源的大小都会极大的减少。因为服务端在比较了缓存标识后,只返回header部分,不需要再将请求的资源主体返回给客户端。

Last-Modified / If-Modified-Since

Last-Modified:表示服务器返回资源的上次修改时间。参数如下:

Last-Modified:<day-name>,<day><month><year><hour>:<minute>:<second>GMT

Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT

If-Modified-Since :表示服务器返回资源的修改时间,与Last-Modified格式相同。服务器会将两者时间进行对比,一致时,表示资源未经修改,返回一个不带有消息主体的 304 响应,浏览器直接从缓存中读取资源;不一致时,表示资源修改,服务器返回请求资源,状态码为 200 。

同样,Last-Modified / If-Modified-Since存在很大的局限性:它的时间精度只能到秒,一秒内进行多次改动,服务器是无法辨别的;还有就是”Ctrl+Z“——修改了文件但是又撤回了修改,实际内容并没有发生改变,但是修改时间还是会变化。也就是为什么它的优先级低于Etag / If-None-Match。

app.get('/index.html',(req, res)=>{
  let getPath = path.resolve(__dirname,'index.html');
  let status = fs.statSync(getPath)
  if(status.mtime.toUTCString() === req.headers['if-modified-since']){
      res.writeHead(304, 'Not Modified')
      res.end()
  } else {
      res.setHeader('Cache-Control', 'max-age=2')
      res.setHeader('Last-Modified', status.mtime.toUTCString())
      res.writeHead(200, 'OK')
      res.end(fs.readFileSync(getPath))
  }
})

07157497bf92e78ded9b355b4ecc058.png
Etag / If-None-Match

Etag表示请求资源特定版本的标识符,只有请求资源的内容发生变化时,Etag才会改变。浏览器会把首次请求到Etag作为If-None-Match,在下次请求时携带。服务器会把接收到的If-None-Match和当前版本的资源的 ETag 进行较,如果匹配(即资源未更改),服务器会返回不带资源的304状态,浏览器直接从缓存中读取资源。不匹配时服务器再次返回请求资源和ETag,状态码为 200。
ETag需要设置加密模块生成,可以引入MD5,这里就不贴代码了,在请求头判断Etag 和If-None-Match匹配即可。

浏览器缓存

cookie

Cookie:服务器传输到本地浏览器的一组数据,这组数据通常用于辨别用户身份、保持登录状态,告知服务器请求是否来源于同一浏览器。它始终在同源http请求中携带,因此它的大小大小被限制在4KB。 Cookie在浏览器关闭后会被删除,当然,浏览器恢复会话时Cookie会被保留,它的生命周期会被延长,这取决于Expires或Max-Age的有效期。它的实际应用场景主要有三个:

  • 网页会话状态(用户的登录状态等)
  • 用户个性化设置(用户自定义主题等)
  • 浏览器行为跟踪(用户行为、操作等)
    创建Cookie :当服务器收到 HTTP 请求时,会响应头里面添加一个 Set-Cookie 选项。浏览器收到后会保存下 Cookie,保存下来的Cookie信息会在之后的每一次请求携带,传递给服务器。
HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry

js获取Cookie:js可通过document.cookie获取并设置Cookie,类似于getter、setter

oldCookies = document.cookie;         //getter
document.cookie = newCookie;          //setter

LocalStorage/SessionStorage

sessionStorage:为每一个给定的资源(键值对)维持一个独立的存储区域,该存储区域在页面会话期间可用(浏览器关闭失效),存储大小为5M

localStorage:与sessionStorage功能相同,区别就是只要不主动删除会一直存在(浏览器关闭无影响)。

// 保存数据
sessionStorage.setItem('key', 'value');
localStorage.setItem('key', 'value');
// 获取数据
let data = sessionStorage.getItem('key');
let data = localStorage.getItem('key');
// 删除保存的数据
sessionStorage.removeItem('key');
localStorage.removeItem('key');
// 删除所有保存的数据
sessionStorage.clear();
localStorage.clear();

IndexedDB

IndexedDB:一种可以让你在用户的浏览器内持久化存储数据的方法,为解决前端无法存储大容量数据而生,它的存储容量非常大,取决与你的硬盘。它的基本模式如下:

  1. 打开数据库。
  2. 在数据库中创建一个对象仓库(object store)。
  3. 启动一个事务,并发送一个请求来执行一些数据库操作,像增加或提取数据等。
  4. 通过监听正确类型的 DOM 事件以等待操作完成。
  5. 在操作结果上进行一些操作(可以在 request 对象中找到)
    特点如下:
  6. 异步
  7. 同源
  8. 事务型数据库
  9. 支持二进制
    直接看代码(添加数据):
const dbName = "dbname";

var request = indexedDB.open(dbName, 2);  //打开数据库,参数为名称和版本号,不存在则自动创建

request.onerror = function(event) {      // 错误处理函数
};

// 数据库创建或升级的时候会触发onupgradeneeded事件
request.onupgradeneeded = function(event) {
  var db = event.target.result;
  
  // 建立对象仓库,keyPath为键路径,不可重复;
  var objectStore = db.createObjectStore("customers", { keyPath: "akey" });

  // 创建索引,unique表示是否唯一,name不唯一,id唯一
  objectStore.createIndex("name", "name", { unique: false });
  objectStore.createIndex("id", "id", { unique: true });

  // 事务的 oncomplete 事件确保对象仓库已经创建完毕
  objectStore.transaction.oncomplete = function(event) {
  
    // 将数据保存到新创建的对象仓库,transaction接收的两个参数第一个为事务希望跨越的对象存储空间的列表为,第二个为操作权限(只读或读写)
    var customerObjectStore = db.transaction("customers", "readwrite").objectStore("customers");          // 仓库对象
      customerObjectStore.add( {
         "akey":1,
         "id": '25802580qwer', // 必须且值唯一
         "name": '张三'
    });
  };
};

可以打开控制台来查看我们建造的仓库、添加的数据;
118606c030094a79fa3273b8332e738.png

当然,除了添加indexedDB还可以实现删、改、查等操作,使用对应的方法即可,感兴趣的同学可以自己实现下。

写在最后

感谢各位小伙伴的阅读,如果这篇文章对你有帮助,麻烦点一个赞吧!有什么错误,也欢迎指正。

参考资源

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Caching

© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
相关推荐
  • 暂无相关文章
  • 评论 抢沙发
    头像
    欢迎您留下宝贵的见解!
    提交
    头像

    昵称

    取消
    昵称表情代码图片

      暂无评论内容