性能優化小冊 - 可編程式緩存:Service Workers

Service Workers 不只能夠應用於 PWA ,PC 端也能夠利用其強大的功能實現一些有趣的優化,網絡中有不少關於 Service Workers 介紹的比較好的文章,基於小冊宗旨,本文並非一篇 Service Workers 的詳細教程。

一些注意的點

Service worker 是一個註冊在指定源和路徑下的事件驅動 workercss

它運行在 worker 上下文中,不能訪問 DOM,相對於驅動應用的主 JavaScript 線程,它運行在其餘線程中,因此不會形成阻塞。html

它採用 JavaScript 控制關聯的頁面或者網站,攔截並修改訪問和資源請求,細粒度地緩存資源。你能夠徹底控制應用在特定情形(最多見的情形是網絡不可用)下的表現。vue

它設計爲徹底異步,同步 API(如 XHR 和 localStorage)不能在 Service worker 中使用。webpack

在已經支持 serivce worker 的瀏覽器的版本中,不少特性沒有默認開啓,web

瞭解瀏覽器對 serivce worker 的支持性。

若是你發現示例代碼在當前版本的瀏覽器中怎麼樣都沒法正常運行,你可能須要開啓一下瀏覽器的相關配置chrome

另外,須要注意的是,出於安全緣由 Service Workers 要求必須在 HTTPS 下才能運行,爲了便於本地開發,localhost 也被瀏覽器認爲是安全源。bootstrap

註冊 Service Worker

使用 ServiceWorkerContainer.register() 函數來註冊站點的 service workerservice sorker 只是一個 JavaScript 腳本)。segmentfault

注意,這個文件的 url 是相對於 origin, 而不是相對於引用它的那個 JS 文件。
<!-- index.html -->
<script>
if ("serviceWorker" in navigator) {
  navigator.serviceWorker.register("/serviceworker.js")
    .then(function(reg) {
      if(reg.installing) {
        console.log('Service worker installing');
      } else if(reg.waiting) {
        console.log('Service worker installed');
      } else if(reg.active) {
        console.log('Service worker active');
      }
    })
    .catch(console.error);
}
</script>

配置緩存清單

Service Worker 註冊以後,瀏覽器會嘗試爲你的頁面或站點安裝並激活它。 數組

install 事件會在註冊完成以後觸發,install 事件通常是被用來填充你的瀏覽器的緩存能力。瀏覽器

爲了達成這個目的,咱們使用了 Service Worker 的新的標誌性的存儲 API — Cache,一個 service worker 上的全局對象,它使咱們能夠存儲網絡響應發來的資源,而且根據它們的請求來生成 key

Cache 接口像 workers 同樣,是暴露在 window 做用域下的,儘管它被定義在 service worker 的標準中, 可是它沒必要必定要配合 service worker 使用。
// serviceworker.js
const cacheName = 'my-cache';
// self 表明 worker 線程自身,即子線程的全局對象
self.addEventListener('install', event => {
  // 用來存放緩存的靜態資源和路由
  const filesToCache = [
    '/',
    '/static/css/reset.css',
    '/static/css/css-loader.css',
    '/static/css/create-version.css',
    '/static/css/bootstrap-grid.css',
    '/static/css/bootstrap.css',
    '/static/js/css-animations.js',
    '/static/js/vue.js',
  ];
  event.waitUntil(
    // caches 是 CacheStorage 的實例子:caches instanceof CacheStorage -> true
    // 使用 CacheStorage.open(cacheName) 打開一個 Cache 對象
    caches.open(cacheName)
    // 將字符串 URL 數組添加到緩存中
    .then(cache => cache.addAll(filesToCache))
    .catch(e => console.error(e))
  );
});

這裏咱們新增了一個 install 事件監聽器,接着在事件上接了一個 ExtendableEvent.waitUntil() 方法。

waitUntil() 用來確保 service worker 不會在 waitUntil() 裏邊的代碼執行完畢以前安裝完成。

若是 caches.open(cacheName)rejected,安裝就會失敗,這個 worker 不會作任何事情(例如:URL 拼寫錯誤)。

注意:首次註冊/激活 Service Worker 線程的頁面須要再次加載纔會受其控制(二次生效)。在成功安裝完成並處於激活狀態以前,Service Worker 線程不會收到 fetch (下文會提到)和 push 事件。

使用 fetch 攔截請求

如今已經將站點資源緩存了,還須要告訴 Service Worker 讓它用這些緩存內容來作點什麼,咱們能夠藉助 fetch API 進行一層攔截。

//fetch 事件處理程序,攔截請求並應用於全部緩存中的靜態資產
self.addEventListener('fetch', e => {
    e.respondWith(
        caches.match(e.request)
        .then(response => response ? response : fetch(e.request))
    )
});

caches.match(event.request) 容許咱們對網絡請求的資源和 cache 裏可獲取的資源進行匹配,查看是否緩存中有相應的資源。這個匹配經過 urlvary header 進行,就像正常的 http 請求同樣。

查看 Fetch API documentation 瞭解更多有關 Request 和 Response 對象的更多信息。

當匹配到 catch 資源時,caches.match(event.request) 就會 resolve,在 then 回調中就能夠直接返回 response

caches.match(e.request)
  .then(response => response)

若是沒有匹配到資源,caches.match(event.request) 就會 reject,能夠告訴瀏覽器直接使用 fetch 進行默認的網絡請求。(意味着在網絡可用的時候能夠直接像服務器請求資源)

caches.match(e.request)
  .then(response => response ? response : fetch(e.request))

更新緩存

更新緩存清單,能夠藉助 activate 事件進行處理,本文案例代碼並無對其進行實現。
只是使用 clients.claim() 對頁面進行控制權獲取,這樣以後打開頁面都會使用版本更新的緩存。

self.addEventListener('activate', e => self.clients.claim());

優化後的效果

Chrome 有一個 chrome://inspect/#service-workers 能夠展現當前設備上激活和存儲的 service worker。還有個 chrome://serviceworker-internals 能夠展現更多細節來容許你開始/暫停/調試 worker 的進程。

經過 Chrome devToolsApplication Tab 咱們能夠查看當前服務工做線程的運行狀況。

serviceworker.js 完整代碼:

const cacheName = 'my-cache';
// self.clients.claim() 取得頁面控制權,這樣以後打開頁面都會使用版本更新的緩存
self.addEventListener('activate', e => self.clients.claim());

self.addEventListener('install', event => {
   // 用來存放緩存的靜態資源和路由
  const filesToCache = [
    '/',
    '/static/css/reset.css',
    '/static/css/css-loader.css',
    '/static/css/create-version.css',
    '/static/css/bootstrap-grid.css',
    '/static/css/bootstrap.css',
    '/static/js/css-animations.js',
    '/static/js/vue.js',
  ];
  
  event.waitUntil(
    // caches 是 CacheStorage 的實例子:caches instanceof CacheStorage -> true
    // 使用 CacheStorage.open(cacheName) 打開一個 Cache 對象
    caches.open(cacheName)
    // 將 filesToCache (字符串 URL 數組)添加到緩存中
    .then(cache => cache.addAll(filesToCache))
    .catch(e => console.error(e))
  );
});
// fetch 進行攔截
self.addEventListener('fetch', e => {
    e.respondWith(
        caches.match(e.request)
        .then(response => {
         return  response ? response : fetch(e.request)
        })
    )
});

延伸閱讀推薦:這篇文章

同系列文章:

相關文獻

相關文章
相關標籤/搜索