欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

前端性能优化(存储篇)

程序员文章站 2022-03-03 08:51:17
...

前端性能优化(存储篇)

缓存

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

  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")
  }