前端罕用缓存技术

前端罕用缓存技术个别分为http缓存和浏览器缓存。

HTTP缓存

Expires

HTTP1.0的内容,服务器应用Expires头来通知Web客户端它能够应用以后正本,直到指定的工夫为止;

Cache-Control

HTTP1.1引入了Cathe-Control,它应用max-age指定资源被缓存多久,次要是解决了Expires一个重大的缺点,就是它设置的是一个固定的工夫点,客户端工夫和服务端工夫可能有误差;

Last-Modified / If-Modified-Since

Last-Modified是服务器通知浏览器该资源的最初批改工夫,If-Modified-Since是申请头带上的,上次服务器给本人的该资源的最初批改工夫。而后服务器拿去比照。
若Last-Modified大于If-Modified-Since,阐明资源有被改变过,则响应整片资源内容,返回状态码200;
若Last-Modified小于或等于If-Modified-Since,阐明资源无新批改,则响应HTTP 304,告知浏览器持续应用以后版本。

Etag / If-None-Match

Etag是服务器依据每个资源生成的惟一标识符,当文件内容被批改时标识符就会从新生成。服务器存储着文件的Etag字段,能够在与每次客户端传送If-none-match的字段进行比拟。如果相等,则示意未修改,响应304;反之,则示意已批改,响应200状态码,返回数据。

浏览器缓存

Storage

简略的缓存形式有cookie,localStorage和sessionStorage,都是浏览器内置贮存性能。

mainfest

html5引入的新规范,能够离线缓存动态文件。

Service Worker

ServiceWorker 介绍

什么是ServiceWorker

Service Worker实质上是充当web应用程序与浏览器之间的代理服务器,也能够在网络可用时作为浏览器和网络间的代理。它们的目标是创立无效的离线体验、拦挡网络申请并依据网络是否可用采取适当的操作,以及更新服务器上的资产。它们还容许拜访推送告诉和后盾同步 API。

ServiceWorker个性

  • Service Worker实质上是一个Web Worker,它独立于Javascript主线程,因而它不能间接拜访DOM,也不能间接拜访window对象,然而能够拜访navigator对象,也能够通过消息传递的形式(如postMessage)与Javascript主线程进行通信。
  • Service Worker独立于Javascript主线程,所以不会造成阻塞。它设计为齐全异步,同步API(如XHR和localStorage不能在Service Worker中应用。
  • Service Worker是基于 HTTPS 的,因为Service Worker中波及到申请拦挡,所以必须应用HTTPS协定来保障平安。如果是本地调试的话,localhost是能够的。
  • Service Worker领有独立的生命周期,与页面无关(关联页面未敞开时,它也能够退出,没有关联页面时,它也能够启动)。注册Service Worker后,浏览器会默默地在背地装置Service Worker。

ServiceWorker生命周期

Service Worker 的生命周期能够分为6个阶段:解析(parsed)、装置(installing)、装置实现(installed)、激活(activating)、激活实现(activated)、闲置(redundant)。

Parsed

当咱们第一次尝试注册 Service Worker 时,用户代理会解析脚本并获取入口点。如果解析胜利(并且满足其余一些要求,例如 HTTPS),咱们将能够拜访 Service Worker 注册对象。其中蕴含无关 Service Worker 的状态及其作用域的信息。

if ('serviceWorker' in navigator) {  navigator.serviceWorker.register('./sw.js')  .then(function(registration) {    console.log("Service Worker Registered", registration);  })  .catch(function(err) {    console.log("Service Worker Failed to Register", err);  })}

Service Worker注册胜利并不意味着它已装置结束或处于激活状态,而只是意味着脚本已胜利解析,它与文档处于同一源上,且源为 HTTPS。注册实现后,服务 Worker 将进入下一个状态。

Installing

一旦 Service Worker 脚本被解析,用户代理就会尝试装置它,并进入装置状态。在 Service Worker 的registration对象中,咱们能够在installing属性中查看此状态。

并且,在installing状态下,install事件会被触发,咱们个别会在这个回调中解决缓存事件。

navigator.serviceWorker.register('./sw.js').then(function(registration) {  if (registration.installing) {    // Service Worker is Installing  }})
self.addEventListener('install', function(event) {  event.waitUntil(    caches.open(currentCacheName).then(function(cache) {      return cache.addAll(arrayOfFilesToCache);    })  );});

如果事件中有 event.waitUntil() 办法,其中的 Promise只有在resolve后,install事件才会胜利。如果 Promise 被回绝,install就会失败,Service Worker 就会变为redundant状态。

self.addEventListener('install', function(event) {  event.waitUntil(   return Promise.reject(); // Failure  );});

Installed / Waiting

如果装置胜利,Service Worker 的状态变为 installed (也叫 waiting )。处于这个状态时, Service Worker 是无效的然而是未激活的 worker,临时没有管制页面的势力,须要期待从以后 worker 取得控制权。

咱们能够在 registration 对象的 waiting 属性中检测到此状态。

navigator.serviceWorker.register('./sw.js').then(function(registration) {  if (registration.waiting) {    // Service Worker is Waiting  }})

咱们能够在这个机会去更新新版本或自动更新缓存。

Activating

在以下状况之一时,处于 Waiting 状态的 worker 的 Activating 状态会被触发:

  • 以后没有处于激活状态的 worker
  • self.skipWaiting() 在 sw.js 中被调用,间接跳过waiting阶段
  • 用户导航来到以后页面,从而开释了前一个 active worker
  • 通过了指定时间段,从而开释了前一个 active worker

在以后状态下,activate事件会被触发,在这个回调中咱们通常用于革除旧缓存。

self.addEventListener('activate', function(event) {  event.waitUntil(    // Get all the cache names    caches.keys().then(function(cacheNames) {      return Promise.all(        // Get all the items that are stored under a different cache name than the current one        cacheNames.filter(function(cacheName) {          return cacheName != currentCacheName;        }).map(function(cacheName) {          // Delete the items          return caches.delete(cacheName);        })      ); // end Promise.all()    }) // end caches.keys()  ); // end event.waitUntil()});

同install事件,如果Promise被reject了,则activate事件失败,Service Worker变为redundant状态。

Activated

如果激活胜利,Service Worker 状态会变成 active ,在这个状态下,Service Worker 是一个能够齐全管制网页的激活 worker,咱们能够在 registration 对象的 active 属性中检测到此状态。

navigator.serviceWorker.register('./sw.js').then(function(registration) {  if (registration.active) {    // Service Worker is Active  }})

当 Service Worker 被胜利激活后,即可解决绑定的 fetch 和 message 事件。

self.addEventListener('fetch', function(event) {  // Do stuff with fetch events});self.addEventListener('message', function(event) {  // Do stuff with postMessages received from document});

Redundant

以下任一状况,Service Worker 都会变成 redundant。

  • install失败
  • activate失败
  • 有新的 Service Worker 将其代替成为现有的激活 worker

Service Worker 离线缓存

Service Worker 最重要的性能之一,就是能够通过缓存动态资源来实现离线拜访咱们的页面。

Service Worker 的缓存基于 CacheStorage,它是一个 Promise 对象,咱们能够通过 caches 来获取它。CacheStorage提供了一些办法,咱们能够通过这些办法来对缓存进行操作。

caches.open(currentCacheName).then(function (cache) {  /** 能够通过cache.put来增加缓存   *  它接管两个参数,第一个参数是Request对象或URL字符串,第二个参数是Response对象  */  cache.put(new Request('/'), new Response('Hello World'));      /** 能够通过cache.addAll来增加缓存资源数组   *  它接管一个参数,这个参数能够是Request对象数组,也能够是URL字符串数组  */  cache.addAll(['/'])  /** 能够通过cache.match来获取缓存   *  它接管一个参数,这个参数能够是Request对象,也能够是URL字符串  */  cache.match('/').then(function (response) {    console.log(response);  });    /** 能够通过cache.delete来删除缓存   *  它接管一个参数,这个参数能够是Request对象,也能够是URL字符串  */  cache.delete('/').then(function () {    console.log('删除胜利');  });  /** 能够通过cache.keys来获取缓存的key   *  而后通过cache.delete来删除缓存  */  cache.keys().then(function (keys) {    keys.forEach(function (key) {      cache.delete(key);    });  });});

缓存资源

咱们在介绍生命周期的时候咱们介绍了在installing状态下会调用install办法,通常咱们会在install事件中缓存一些资源。

self.addEventListener('install', function (event) {  event.waitUntil(    caches.open(currentCacheName).then(function (cache) {      return cache.addAll([        '/',        '/index.css',        '/axios.js',        '/index.html'      ]);    })  );});

下面的代码中咱们缓存了一些资源,所以咱们能够在fetch事件中获取并返回刚刚缓存的资源。

self.addEventListener('fetch', function (event) {  event.respondWith(    caches.match(event.request).then(function (response) {      if (response) {        return response;      }      return fetch(event.request);    })  );});

下面的代码中咱们应用caches.match来匹配申请,如果匹配到了,那么就返回缓存的资源,如果没有匹配到,那么就从网络中获取资源。

缓存更新

在下面的步骤中,咱们曾经缓存了咱们的资源,并且该资源并不会随着咱们代码或者资源的更改而更新缓存。因而,咱们能够通过版本号来管制更新。

介绍生命周期时,咱们有理解到在activating状态下会触发activate回调,在该回调中咱们能够革除旧缓存,而后在install事件中缓存新的资源。

const version = '2.0';const currentCache = 'my-cache' + version;self.addEventListener('activate', function (event) {  event.waitUntil(    caches.keys().then(function (cacheNames) {      return Promise.all(        cacheNames.map(function (cacheName) {          if (cacheName !== currentCache) {            return caches.delete(cacheName);          }        })      );    })  );});

卸载

当咱们的页面不再须要Service Worker的时候,能够通过在新版本里应用unregister进行卸载。

if ('serviceWorker' in navigator) {  navigator.serviceWorker.ready.then(registration => {    registration.unregister();  });}

须要留神的是,Service Worker卸载并不会删掉咱们之前缓存的资源,所以在卸载之前咱们须要革除所有的缓存。

缓存策略

从下面的例子能够看出,Service Worker的缓存是通过Cache接口和fetch事件独特实现的。通过Cache接口和fetch事件能够实现多种缓存策略。

仅缓存 (Cache only)

实用于你认为属于该“版本”网站动态内容的任何资源,匹配的申请将只会进入缓存。

仅网络 (Network only)

与“仅缓存”相同,“仅限网络”是指申请通过 Service Worker 传递到网络,而无需与 Service Worker 缓存进行任何交互。

缓存优先 (Cache first)

该策略流程如下:

  • 申请达到缓存。如果资源位于缓存中,请从缓存中提供。
  • 如果申请不在缓存中,请转到网络。
  • 网络申请实现后,将其增加到缓存中,而后从网络返回响应。

该策略实用于动态资源的缓存,它能够绕过 HTTP 缓存可能启动的服务器执行任何内容新鲜度查看,从而放慢不可变资源的速度。

网络优先 (Network first)

该策略如下:

  • 先返回网络申请一个申请,而后将响应放入缓存中。
  • 如果您日后处于离线状态,则会回退到缓存中该响应的最新版本。

此策略非常适合 HTML 或 API 申请,当您想在线获取资源的最新版本,同时又心愿离线能够拜访到最新的可用版本。

提早验证 (Stale-while-revalidate)

该机制与最初两种策略相似,但其过程优先思考资源访问速度,同时还在后盾放弃更新。策略大抵如下:

  • 在第一次申请获取资源时,从网络中提取资源,将其放入缓存中并返回网络响应。
  • 对于后续申请,首先从缓存提供资源,而后“在后盾”从网络从新申请该资源,并更新资源的缓存条目。
  • 对于尔后的申请,您将收到在上一步中从缓存中搁置的最初一个网络提取的版本。

Service Worker 后盾同步

假如用户在咱们的页面上操作了数据并提交,此时正好进入一个网络极差甚至断网的环境里,用户只能看着始终处于loading状态的页面,直到失去急躁敞开页面,这时申请就曾经被中断了。

下面这种状况裸露了两个问题:

  • 一般页面会随着页面敞开而终止
  • 网络极差或无网络状况下没用一种解决方案可能解决并维持以后申请以待有网时复原申请

后盾同步是构建在 Service Worker 过程之上的另一个性能,它容许一次性或以一个工夫距离申请后盾数据同步。咱们能够充分利用这一性能躲避以上问题。

工作流程

  • 在Service Worker中监听sync事件
  • 在浏览器中发动后盾同步sync
  • 就会触发Service Worker的sync事件,在该监听的回调中进行操作,例如向后端发动申请
  • 而后能够在Service Worker中对服务端返回的数据进行解决

页面触发同步

if ('serviceWorker' in navigator) {  navigator.serviceWorker.register('./sw.js')    navigator.serviceWorker.ready.then(function (registration) {    let tag = "data_sync";    document.getElementById('submit-btn').addEventListener('click', function () {      registration.sync.register(tag).then(function () {        console.log('后盾同步已触发', tag);      }).catch(function (err) {        console.log('后盾同步触发失败', err);      });    });  })}

因为后盾同步性能须要在Service Worker注册实现后触发,所以咱们能够应用navigator.serviceWorker.ready期待注册实现筹备好之后应用 registration.sync.register 注册同步事件。

registration.sync 会返回一个SyncManager对象其中蕴含register办法和getTags办法。

SW监听同步事件

当页面触发同步事件后,咱们须要通过Service Worker来解决sync事件。

self.addEventListener('sync', function (e) {  let init = { method: 'GET' };    switch (e.tag){    case "data_sync":      let request = new Request(`xxxxx/sync`, init);      e.waitUntil(        fetch(request).then(function (response) {          return response;        })      );      break;  }});

Taro我的项目集成

实践说完了,接下来咱们能够在taro我的项目里实际接入Service Worker。

俗话说,站在伟人的肩膀上看世界。

当初市面上实现SW的工具十分多,其中google团队提供了一个非常弱小且欠缺的插件 workbox-webpack-plugin ,接下来咱们将通过这个插件实现Service Worker的离线缓存性能。

插件配置

workbox-webpack-plugin提供了两个类名为 GenerateSW 和 InjectManifest,接下来咱们通过应用GenerateSW来实现预缓存文件和简略的运行时缓存需要。

在taro我的项目负责打包的config文件中退出以下配置:

const { GenerateSW } = require('workbox-webpack-plugin');const config = {  ...  h5: {    ...    webpackChain(chain) {        ...      chain.plugin('generateSW').use(new GenerateSW({        clientsClaim: true,        skipWaiting: true,        runtimeCaching: [          {            urlPattern: /.\/*/, // 须要缓存的门路            handler: 'StaleWhileRevalidate', // 缓存策略            options: {              cacheName: 'my-webcache',              expiration: {                maxEntries: 2000,              },            },          }],      }));    }  }}

退出以上配置后,咱们运行build命令能够发现该插件为咱们主动生成了Service Worker文件。

Service Worker注册

生成Service Worker文件之后咱们须要在我的项目中进行注册。

在register文件中解决Service Worker的生命周期、状态等信息。

import { register } from 'register-service-worker';register('./service-worker.js', {  registrationOptions: { scope: './' },  ready(registration) {    console.log('Service worker is active.', registration);  },  registered(registration) {    console.log('Service worker has been registered.', registration);  },  cached(registration) {    console.log('Content has been cached for offline use.', registration);  },  updatefound(registration) {    console.log('New content is downloading.', registration);  },  updated(registration) {    console.log('New content is available; please refresh.', registration);  },  offline() {    console.log('No internet connection found. App is running in offline mode.');  },  error(error) {    console.error('Error during service worker registration:', error);  },});

在app.ts中引入该文件,咱们就实现了简略的Service Worker的引入。接下来把我的项目启动,让咱们看看SW是否失效。

在失常网络环境中,能够看到咱们发动第一次拜访的申请列表。

在把网络设置成离线状态后,能够看到咱们的申请仍然失常返回,并走的是Service Worker的缓存。

咱们也能够在控制台看到所有缓存的文件列表。

总的来说,Service Worker是一个十分弱小的性能,除了以上介绍的离线缓存和后盾同步性能,还能够通过SW实现音讯推送、多页面通信等等性能。

(本文作者:龚思晗)