3

web性能优化(存储方面)

个人理解, 存储方面的优化其实也是为了减少 http 请求, 节省网络传输带来耗时影响...
把我们需要的文件资源缓存在本地, 或者缓存在代理服务器, 源服务器等. 都是为了优化资源访问的过程...这里我们单纯的从前端能操作的存储来讲, 服务端的手段会介绍下,毕竟全栈也是我们的目标,ememm...

1.浏览器缓存(浏览器)

浏览器缓存机制有几个方面,它们按照获取资源时请求的优先级依次排列如下
Memory Cache/Disk Cache, Service Worker Cache, HTTP Cache, Push Cache(http2).

HTTP Cache(重点)

我们可以根据不同的资源缓存需求去设置的缓存策略, 当然事先将资源进行分类, 然后才去设定对应的缓存策略;
我们可以把http缓存策略按阶段分为缓存存储策略, 缓存过期策略, 缓存协商对比策略三个阶段来理解.
先看下关于缓存头的几个字段, 优先级 http1.1字段高

version header key desc 存储策略 过期策略 协商对比策略
HTTP1.0 Pragma 指定缓存机制: no-cache
HTTP1.1 Cache-Control 指定缓存机制
HTTP1.0 Expires 指定缓存的过期时间
HTTP1.0 res: Last-Modified 资源最后一次的修改时间
HTTP1.0 req: If-Modified-Since 缓存校验字段,值为资源最后一次的修改时间, 即上次收到的Last-Modified值
HTTP1.1 res: ETag 唯一标识请求资源的字符串
HTTP1.1 req: If-None-Match 缓存校验字段, 值为唯一标识请求资源的字符串, 即上次收到的ETag值
缓存存储策略

用来确定 Http 响应内容是否可以被客户端缓存,以及可以被哪些客户端缓存;
这个策略的作用只有一个, 用于决定 Http 响应内容是否可缓存到客户端.

  • cache-control的值设置
public:    资源将被客户端和代理服务器缓存
private:    资源仅被客户端缓存, 代理服务器不缓存
no-store    请求和响应都不缓存
no-cache    相当于max-age:0,must-revalidate即资源被缓存,但是缓存立刻过期, 同时下次访问时强制验证资源有效性
max-age    缓存资源, 但是在指定时间(单位为秒)后缓存过期
s-maxage    同上, 依赖public设置, 覆盖max-age, 且只在代理服务器上有效.
max-stale    指定时间内, 即使缓存过时, 资源依然有效
min-fresh    缓存的资源至少要保持指定时间的新鲜期
must-revalidation / proxy-revalidation    如果缓存失效, 强制重新向服务器(或代理)发起验证(因为max-stale等字段可能改变缓存的失效时间)    
only-if-cached    仅仅返回已经缓存的资源, 不访问网络, 若无缓存则返回504    
no-transform    强制要求代理服务器不要对资源进行转换, 禁止代理服务器对 Content-Encoding, Content-Range, Content-Type字段的修改(因此代理的gzip压缩将不被允许)
  • Pragma
用来兼容 http1.0, 其值为 no-cache
缓存过期策略

客户端用来确认存储在本地的缓存数据是否已过期,进而决定是否要发请求到服务端获取数据;
这个策略的作用也只有一个,那就是决定客户端是否可直接从本地缓存数据中加载数据并展示(否则就发请求到服务端获取)

通过设置:
Expires: Wed, 29 Apr 2020 07:45:47 GMT 缓存到期时间
Cache-Control: max-age=6000 缓存资源, 但是在指定时间6000s(单位为秒)后缓存过期
// Cache-Control优先
假设请求资源于5月1日缓存, 且在5月7日过期(时间是相对于请求的时间)
这期间都会命中强缓存
  • 强缓存

对于常规请求,只要存在该资源的缓存,且Cache-Control:max-age或者expires没有过期,
那么就能命中强缓存;浏览器便不会向服务器发送请求, 而是直接读取缓存.
Chrome下的现象是 200 OK (from disk cache) 或者 200 OK (from memory cache)

缓存协商对比策略

将缓存在客户端的数据标识发往服务端,服务端通过标识来判断客户端 缓存数据是否仍有效,进而决定是否要重发数据;
客户端检测到数据过期或浏览器刷新后,往往会重新发起一个 http 请求到服务器,服务器此时并不急于返回数据,而是看请求头有没有带标识( If-Modified-Since、If-None-Match)过来,如果判断标识仍然有效,则返回304告诉客户端取本地缓存数据来用即可(这里要注意的是你必须要在首次响应时输出相应的头信息(Last-Modified、ETag)到客户端.

协商缓存
缓存过期后, 继续请求该资源, 对于现代浏览器, 拥有如下两种做法:

  • Etag/If-None-Match(优先级高)
根据上次响应中的ETag_value, 自动往request header中添加If-None-Match字段. 
服务器收到请求后, 拿If-None-Match字段的值与资源的ETag值进行比较, 
若相同, 则命中协商缓存, 返回304响应.
  • Last-Modified/If-Modified-Since
根据上次响应中的Last-Modified_value, 自动往request header中添加If-Modified-Since字段. 
服务器收到请求后, 拿If-Modified-Since字段的值与资源的Last-Modified值进行比较, 
若相同, 则命中协商缓存, 返回304响应

# 缓存协商时间计算
// 利用response: Date 和 Last-Modified返回的时间计算出缓存过期日期
缓存过期日期 = (客户端日期new Date) + (Date_value - Last-Modified_value) / 10
# 所以, 当我们更改了本地时间后可能会出现一些问题; http1.1引入Etag解决了

// 然后缓存过期后, 会再次请求服务端,并携带上 Last-Modified 指定的时间去服务器对比
并根据服务端的响应状态决定是否要从本地加载缓存数据.

说了这么多, 怎么设置这些头部呢?

只能说在客户端(浏览器下)能做的有限, 基本都是在服务端(Nginx)设置的...emm; 所以前端必须学server方面的知识啊!!!是不是有点崩溃呢? 快起来我还能学...
单纯通过前端 html 设置, 也需要浏览器支持,基本没啥用

<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate"/>

Memory Cache/Disk Cache

MemoryCache,是指存在内存中的缓存.从优先级上来说,它是浏览器最先尝试去命中的一种缓存.从效率上来说,它是响应速度最快的一种缓存; 浏览器有一套自己的规则, 会将较小的文件 base64图片,css,js 等放进去, 内存的分配是有限的, 如果文件过大或者分配内存不足, 文件会放到 Disk 磁盘中...

Service Worker Cache(PWA的基础之一)

Service Worker MDN
Service Worker 是一种独立于主线程之外的 Javascript 线程.它脱离于浏览器窗体,因此无法直接访问 DOM.这样独立的个性使得 Service Worker 的“个人行为”无法干扰页面的性能,这个“幕后工作者”可以帮我们实现离线缓存、消息推送和网络代理等功能.我们借助 Service worker 实现的离线缓存就称为 Service Worker Cache.

Service Worker 的生命周期包括 install、active、working 三个阶段.一旦 Service Worker 被 install,它将始终存在,只会在 active 与 working 之间切换,除非我们主动终止它.这是它可以用来实现离线存储的重要先决条件.

  • Service Worker实现离线缓存
// mian.js
if ('serviceWorker' in navigator) {
  window.navigator.serviceWorker.register('/demo.js').then(
   function () {
      console.log('注册成功')
    }).catch(err => {
      console.error("注册失败")
    })
} else {
  console.error("The current browser doesn't support service workers")
}
// demo.js: 缓存的文件分别是 test.html,test.css 和 test.js
// Service Worker会监听 install事件,我们在其对应的回调里可以实现初始化的逻辑  
self.addEventListener('install', event => {
  event.waitUntil(
    // 考虑到缓存也需要更新,open内传入的参数为缓存的版本号
    caches.open('test-v1').then(cache => {
      return cache.addAll([
        // 此处传入指定的需缓存的文件名
        '/test.html',
        '/test.css',
        '/test.js'
      ])
    })
  )
})

// Service Worker会监听所有的网络请求,网络请求的产生触发的是fetch事件,
// 我们可以在其对应的监听函数中实现对请求的拦截,进而判断是否有对应到该请求的缓存,
// 实现从Service Worker中取到缓存的目的
// Server Worker 对协议是有要求的,必须以 https 协议为前提
self.addEventListener('fetch', event => {
  event.respondWith(
    // 尝试匹配该请求对应的缓存值
    caches.match(event.request).then(res => {
      // 如果匹配到了,调用Server Worker缓存
      if (res) {
        return res;
      }
      // 如果没匹配到,向服务端发起这个资源请求
      return fetch(event.request).then(response => {
        if (!response || response.status !== 200) {
          return response;
        }
        // 请求成功的话,将请求缓存起来.
        caches.open('test-v1').then(function(cache) {
          cache.put(event.request, response);
        });
        return response.clone();
      });
    })
  );
});

Push Cache

了解不多, 参考此篇文章https://jakearchibald.com/201...

  • 一些特性
- Push Cache 是缓存的最后一道防线.
浏览器只有在 Memory Cache、HTTP Cache 和 Service Worker Cache 均未命中的情况下才会去询问 Push Cache.
- Push Cache 是一种存在于会话阶段的缓存,当 session 终止时,缓存也随之释放.
- 不同的页面只要共享了同一个 HTTP2 连接,那么它们就可以共享同一个 Push Cache

2.浏览器本地存储

这个就是我们前端可以操作的部分了...

cookie, localStorage. sessionStorage

参考我之前写的

IndexedDB

IndexedDB MDN
这里也不做细讲了, 参考张鑫旭大佬的文章https://www.zhangxinxu.com/wo...

参考

http header
Service Worker MDN
http2 push
IndexedDB MDN
web worker
IndexedDB Demo
Service Worker 参考文章


a_dodo
2.4k 声望1k 粉丝

天下事有难易乎?