ServiceWorker 緩存離線化

概述

Service Worker 是HTML5 的一個新特性,主要用來作持久的離線緩存javascript

爲何要使用ServiceWorker

在公司業務中,由於常常要處理性能優化方面的需求,使用傳統的性能優化手段,知足了大多數業務場景。可是,若是目標用戶手機性能以及網絡廣泛較差的狀況下(例如東南亞、印度等海外市場),瓶頸就在於DNS查詢,TCP的創建時間,採用常規的優化手段就顯得捉襟見肘。此時,咱們項目組有嘗試採用離線緩存方案,即將靜態資源緩存到本地,經過攔截代理請求,讀取本地文件,加快訪問速度。html

ServiceWorker的目的

這個 API 的惟一目的就是解放主線程,Web Worker 是脫離在主線程以外的,將一些複雜的耗時的活交給它幹,完成後經過 postMessage 方法告訴主線程,而主線程經過 onMessage 方法獲得 Web Worker 的結果反饋。java

功能和特性

  • Service Worker擁有本身獨立的 worker 線程,獨立於當前網頁線程
  • 離線緩存靜態資源
  • 攔截代理請求和響應
  • 可自定義響應內容
  • 能夠經過postMessage向主線程發送消息
  • 沒法直接操做DOM
  • 必須在HTTPS環境下工做或 localhost / 127.0.0.1 (自身安全機制)
  • 經過Promise異步實現
  • Service Worker安裝(installing)完成後,就會一直存在,除非手動卸載(unregister)

生命週期

Service Worker 的生命週期徹底獨立於網頁webpack

  • 註冊 (register)
  • 安裝 (install)
  • 激活 (activate)

在這裏插入圖片描述

一般使用 service worker 只須要如下幾個步驟:git

  • 1. 檢測是否支持serivceworker

首先,檢測當前環境是否支持 service worker,可使用 'serviceWorker' in navigator 進行檢測。github

  • 2. 註冊(register)

若是支持,可使用 navigator.serviceWorker.register('./sw.js'),在當前主線程中註冊 service worker。若是註冊成功,service worker 則在 ServiceWorkerGlobalScope環境中運行; 須要注意的是: 當前環境沒法操做DOM,且和主線程之間相互獨立(即線程之間不會相互阻塞)。web

  • 3. 安裝(install)

而後,後臺開始安裝service worker,通常在此過程當中,開始緩存一些靜態資源文件。瀏覽器

  • 4. 激活(active)

安裝成功以後,準備進行激活 service worker,一般在激活狀態下,主要進行緩存清理,更新service worker等操做。緩存

  • 5. 使用(activing)

激活成功後,,service worker 就能夠控制當前頁面了。須要注意的是,只有在service worker成功激活後,才具備控制頁面的能力,通常在第一次訪問頁面時,service worker第一次建立成功,並無激活,只有當刷新頁面,再次訪問以後,才具備控制頁面的能力。安全

  • 6. 卸載(unregister)

緩存顆粒化

  1. 緩存 *.html 靜態資源文件

緩存收益和成本

  • 收益
    1. 省去創建tcp的鏈接時長,加快首屏加載速度
    2. 減小靜態資源服務器的負載
  • 成本
    1. 數據不一致問題(更新策略)
    2. 代碼維護成本(緩存文件)

項目演示

本項目在第一次安裝serverworker以後,能夠在控制檯看到如下信息:

在這裏插入圖片描述

查看對應請求的靜態資源信息:

能夠看到,第一次安裝serviceworker時,讀取到的靜態資源並無緩存。

在這裏插入圖片描述
刷新瀏覽器以後,咱們再看一下這些靜態資源:

能夠看到,靜態資源以及被serviceworker緩存起來了。

在這裏插入圖片描述

咱們再來查看當前serviceworker安裝狀況:

能夠看到,serviceworker已經處於激活狀態。

在這裏插入圖片描述

最後,看一下離線功能的效果:

是否是很神奇,在離線狀態下咱們的頁面也是可以展現出數據的。 其中,離線的原理就是利用了serviceworker中,fetchcacheStorage這兩個接口,將請求進行攔截,將響應進行緩存

在這裏插入圖片描述

源碼實現

該源碼實現瞭如下幾個功能:

  • 強制更新 經過self.skipWaiting(),若是檢測到新的service worker文件,就會當即替換掉舊的。
  • 緩存靜態資源 cache.addAll(cacheFiles) 經過這個接口實現
  • 攔截請求 經過監聽fetch事件,能夠攔截當前頁全部請求self.addEventListener('fetch',function(e){})
  • 緩存響應 將響應內容加入緩存cache.put(evt.request, response)
// 緩存靜態資源文件列表
let cacheFiles = [
  './test.js',
  './index.html',
  './src/img/yy.png'
]
// serviceworker使用版本
let __version__ = 'cache-v2'

// 緩存靜態資源
self.addEventListener('install', function (evt) {
  // 強制更新sw.js
  self.skipWaiting()
  evt.waitUntil(
    caches.open(version).then(function (cache) {
      return cache.addAll(cacheFiles)
    })
  )
})

// 緩存更新
self.addEventListener('active', function (evt) {
  evt.waitUntil(
    caches.keys().then(function (cacheNames) {
      return Promise.all(
        cacheNames.map(function (cacheName) {
          if (cacheName !== version) {
            return caches.delete(cacheName)
          }
        })
      )
    })
  )
})

// 請求攔截
self.addEventListener('fetch', function (evt) {
  console.log('處理fetch事件:', evt.request.url)
  evt.respondWith(
    caches.match(evt.request).then(function (response) {
      if (response) {
        console.log('緩存匹配到res:', response)
        return response
      }
      console.log('緩存未匹配對應request,準備從network獲取', caches)
      return fetch(evt.request).then(function (response) {
        console.log('fetch獲取到的response:', response)
        caches.open(version).then(function (cache) {
          cache.put(evt.request, response)
          return response
        })
      })
    }).catch(function (err) {
      console.error('fetch 接口錯誤', err)
      throw err
    })
  )
})

複製代碼

請參考: 源碼地址

相關文章
相關標籤/搜索