前端性能优化(存储篇)

前端性能优化(存储篇)

缓存

浏览器缓存机制四个方面,优先级依次排列如下:

  1. Memory Cache
  2. Service Worker Cache
  3. HTTP Cache
  4. Push Cache

强缓存

  • 强缓存是利用 http 头中的 Expires 和 Cache-Control 两个字段来控制的。强缓存中,当请求再次发出时,浏览器会根据其中的 expirescache-control 判断目标资源是否“命中”强缓存,若命中则直接从缓存中获取资源,不会再与服务端发生通信

  • 命中强缓存的情况下,返回的 HTTP 状态码为 200

  1. expires(expires: Wed, 11 Sep 2019 16:12:18 GMT)
  • expires 是一个时间戳,接下来如果我们试图再次向服务器请求资源,浏览器就会先对比本地时间和 expires 的时间戳,如果本地时间小于 expires 设定的过期时间,那么就直接去缓存中取这个资源。
  • 缺陷:对“本地时间”的依赖。如果服务端和客户端的时间设置不同,或者我直接手动去把客户端的时间改掉,那么 expires 将无法达到我们的预期。
  1. Cache-Control (cache-control: max-age=31536000)
  • HTTP1.1 新增了 Cache-Control 字段来完成 expires 的任务,可以视作是 expires 的完全替代方案。

  • Cache-Control 的优先级高于 expires。

max-age 与 s-maxage

  • max-age=设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒)

  • s-maxage=覆盖max-age 或者 Expires 头,但是仅适用于共享缓存(比如各个代理),并且私有缓存中它被忽略。s-maxage 就是用于表示 cache 服务器上(比如 cache CDN)的缓存的有效时间的,并只对 public 缓存有效

  • s-maxage仅在代理服务器中生效,客户端中我们只考虑max-age。


 // cache-control: max-age=3600, s-maxage=31536000

no-store与no-cache

  • no-cache 绕开了浏览器,为资源设置了 no-cache 后,每一次发起请求都不会再去询问浏览器的缓存情况,而是直接向服务端去确认该资源是否过期。(no-cache 可以在本地缓存,可以在代理服务器缓存,但是这个缓存要服务器验证才可以使用 )

  • no-store 就是不使用任何缓存策略。在 no-cache 的基础上,它连服务端的缓存确认也绕开了,只允许你直接向服务端发送请求、并下载完整的响应。

public 与 private

  • 为资源设置了 public,那么它既可以被浏览器缓存,也可以被代理服务器缓存;如果设置了 private,则该资源只能被浏览器缓存。private 为默认值。

  • 但多数情况下,public 并不需要我们手动设置(max-age有的话,表示响应是可以缓存的)

协商缓存

  • 浏览器与服务器合作之下的缓存策略。协商缓存机制下,浏览器需要向服务器去询问缓存的相关信息,进而判断是重新发起请求、下载完整的响应,还是从本地获取缓存的资源。

  • 如果服务端提示缓存资源未改动(Not Modified),资源会被重定向到浏览器缓存,这种情况下网络请求对应的状态码是 304

Last-Modified 与 Etag

  • Last-Modified 是一个时间戳,如果启用了协商缓存,它会在首次请求时随着 Response Headers 返回:

    // Last-Modified: Fri, 27 Oct 2017 06:35:57 GMT

    // 后面的每次请求时,会带上一个叫 If-Modified-Since 的时间戳字段,它的值正是上一次 response 返回给它的 last-modified 值:

    // If-Modified-Since: Fri, 27 Oct 2017 06:35:57 GMT

  • 服务器接收到这个时间戳(If-Modified-Since)后,会比对该时间戳和资源在服务器上的最后修改时间是否一致,从而判断资源是否发生了变化

  • 缺点:(服务器并没有正确感知文件的变化)

  1. 编辑了文件,但没有改变文件内容。服务端并不清楚我们是否真正改变了文件,它仍然通过最后编辑时间进行判断。因此这个资源在再次被请求时,会被当做新资源,进而引发一次完整的响应——不该重新请求的时候,也会重新请求。

  2. 修改文件的速度过快。由于 If-Modified-Since 只能检查到以秒为最小计量单位的时间差,所以它是感知不到这个改动的——该重新请求的时候,反而没有重新请求了。


  • Etag(由服务器为每个资源生成的唯一的标识字符串),作为 Last-Modified 的补充。首次请求时,我们会在响应头里获取到一个最初的标识符字符串。

    // ETag: W/"2a3b-1602480f459"

    // 下一次请求时,请求头里就会带上一个值相同的、名为 if-None-Match 的字符串供服务端比对了:
    // If-None-Match: W/"2a3b-1602480f459"

  • 缺点: Etag 的生成过程需要服务器额外付出开销,会影响服务端的性能。

  • Etag 在感知文件变化上比 Last-Modified 更加准确,优先级也更高。当 Etag 和 Last-Modified 同时存在时,以 Etag 为准。

MemoryCache

  • MemoryCache,是指存在内存中的缓存。优先级上是浏览器最先尝试去命中的一种缓存。效率上是响应速度最快的一种缓存。但也是“短命”的。它和渲染进程“生死相依”,当进程结束后,也就是 tab 关闭以后,内存里的数据也将不复存在。

  • Base64格式的图片几乎永远可以被塞进 memory cache,另外体积不大的 JS、CSS 文件有较大地被写入内存。

Service Worker Cache

  • Service Worker 是一种独立于主线程之外的 Javascript 线程。它脱离于浏览器窗体,因此无法直接访问 DOM。

  • 可以帮我们实现离线缓存、消息推送和网络代理等功能

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


    window.navigator.serviceWorker.register('/vfrank.js').then(
        function() {
            console.log('注册成功')
        }).catch(err => {
        console.error("注册失败")
    })

  • 在 vfrank.js 中,我们进行缓存的处理。假设我们需要缓存的文件分别是 vfrank.html,vfrank.css 和 vfrank.js:

   // Service Worker会监听 install事件,我们在其对应的回调里可以实现初始化的逻辑  
    self.addEventListener('install', event => {
        event.waitUntil(
            // 考虑到缓存也需要更新,open内传入的参数为缓存的版本号
            caches.open('vfrank-v1').then(cache => {
                return cache.addAll([
                    // 此处传入指定的需缓存的文件名
                    '/vfrank.html',
                    '/vfrank.css',
                    '/vfrank.js'
                ])
            })
        )
    })

    // Service Worker会监听所有的网络请求,网络请求的产生触发的是fetch事件,我们可以在其对应的监听函数中实现对请求的拦截,进而判断是否有对应到该请求的缓存,实现从Service Worker中取到缓存的目的
    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('vfrank-v1').then(function(cache) {
                        cache.put(event.request, response);
                    });
                    return response.clone();
                });
            })
        );
    });

Push Cache

  • 是指 HTTP2 在 server push 阶段存在的缓存

  • Push Cache 是缓存的最后一道防线。浏览器只有在 Memory Cache、HTTP Cache 和 Service Worker Cache 均未命中的情况下才会去询问 Push Cache。

  • Push Cache 是一种存在于会话阶段的缓存,当 session 终止时,缓存也随之释放。

  • 不同的页面只要共享了同一个 HTTP2 连接,那么它们就可以共享同一个 Push Cache。


本地存储

Cookie (Cookie 的详细内容,可以在 Chrome 的 Application 面板中查看)

  • Cookie 是有体积上限最大只能有 4KB。当 Cookie 超过 4KB 时,它将被裁切。

  • 通过响应头里的 Set-Cookie 指定要存储的 Cookie 值


    // Set-Cookie: name=xiuyan; domain=xiuyan.me

Web Storage

  • Web Storage 是 HTML5 专门为浏览器存储而提供的数据存储机制。它又分为 Local Storage 与 Session Storage。

  • 生命周期:Local Storage 是持久化的本地存储,存储在其中的数据是永远不会过期的,使其消失的唯一办法是手动删除;而 Session Storage 是临时性的本地存储,它是会话级别的存储,当会话结束(页面被关闭)时,存储内容也随之被释放。

  • 作用域:Local Storage、Session Storage 和 Cookie 都遵循同源策略。但 Session Storage 特别的一点在于,即便是相同域名下的两个页面,只要它们不在同一个浏览器窗口中打开,那么它们的 Session Storage 内容便无法共享。


    // 存储数据:setItem()
    localStorage.setItem('user_name', 'xiuyan')

    // 读取数据: getItem()
    localStorage.getItem('user_name')

    // 删除某一键名对应的数据: removeItem()
    localStorage.removeItem('user_name')

    // 清空数据记录:clear()
    localStorage.clear()

IndexDB

  • IndexDB 是一个运行在浏览器上的非关系型数据库
  1. 打开/创建一个 IndexDB 数据库(当该数据库不存在时,open 方法会直接创建一个名为 xiaoceDB 新数据库)。

    // 后面的回调中,我们可以通过event.target.result拿到数据库实例
    let db
        // 参数1位数据库名,参数2为版本号
    const request = window.indexedDB.open("xiaoceDB", 1)
        // 使用IndexDB失败时的监听函数
    request.onerror = function(event) {
            console.log('无法使用IndexDB')
        }
        // 成功
    request.onsuccess = function(event) {
        // 此处就可以获取到db实例
        db = event.target.result
        console.log("你打开了IndexDB")
    }

  1. 创建一个 object store(object store 对标到数据库中的“表”单位)。

    // onupgradeneeded事件会在初始化数据库/版本发生更新时被调用,我们在它的监听函数中创建object store
    request.onupgradeneeded = function(event) {
        let objectStore
            // 如果同名表未被创建过,则新建test表
        if (!db.objectStoreNames.contains('test')) {
            objectStore = db.createObjectStore('test', { keyPath: 'id' })
        }
    }

  1. 构建一个事务来执行一些数据库操作,像增加或提取数据等。

      // 创建事务,指定表格名称和读写权限
    const transaction = db.transaction(["test"], "readwrite")
        // 拿到Object Store对象
    const objectStore = transaction.objectStore("test")
        // 向表格写入数据
    objectStore.add({ id: 1, name: 'xiuyan' })

  1. 通过监听正确类型的事件以等待操作完成。

  // 操作成功时的监听函数
  transaction.oncomplete = function(event) {
          console.log("操作成功")
      }
      // 操作失败时的监听函数
  transaction.onerror = function(event) {
      console.log("这里有一个Error")
  }

前端性能优化(渲染篇)
前端性能优化(存储篇)
前端性能优化(图片篇)

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值