藉助Service Worker和cacheStorage緩存及離線開發

1、緩存和離線開發

說得HTML5離線開發,咱們一般第一反應是使用html5 manifest緩存技術,此技術已經出現不少年了,我之前屢次瞭解過,也見過一些實踐案例,可是卻從未在博客中介紹過,由於並不看好。html

爲何不看好呢?用一句話解釋就是「投入產出比有些低」。前端

對於web應用,掉線不能使用是理所固然的,毫不會有哪一個開發人員會由於網頁在沒網的時候打不開被測試MM提bug,或者被用戶投訴,因此,咱們的web頁面不支持離線徹底不會有什麼影響。但若是咱們但願支持離線,會發現,我投入的精力和成本啊還真不小,就說一點,html5 manifest緩存技術須要服務端配合直接就讓成本蹭蹭蹭的上去了,由於已經不只僅是前端這一個工種的工做了,極可能就須要跨團隊協做,這事情立馬一會兒就囉嗦了,開發時候囉嗦,之後維護也囉嗦。html5

而支持離線的收益,我估算了下,比支持無障礙訪問收益還要低。因此,站在商業的角度講,若是一項技術成本比較高,收益比較小,是並不建議實際應用的。開發人員喜歡在重要的項目中玩些時髦的酷酷的新技術自嗨是能夠理解的,但前提是收益明顯。若是隻是自嗨,留下爛攤,有必要警戒了,業務意識和職業素養說明還有待提升。jquery

鋪墊這麼多,就是要轉折說下本文要介紹的緩存技術。git

本文所要介紹的基於Service Worker和cacheStorage緩存及離線開發,套路很是固定,無侵入,且語言純正,直接複製粘貼就能夠實現緩存和離線功能,純前端,無需服務器配合。一個看上去很酷的功能只要複製粘貼就能夠實現,絕對是成本極低的,小白中的小白也能上手。github

因爲成本幾乎能夠忽略不計,此時離線功能支持的投入產出比至關高了,經驗告訴我這種技術之後流行和普及的可能性很是高,因而火燒眉毛分享給你們。web

在開始案例前,咱們有必要先了解幾個概念。數組

2、通俗易懂的方式介紹Service Worker

Service Worker直白翻譯就是「服務人員」,看似不經意的翻譯,實際上卻正道出了Service Worker的精髓所在。promise

舉個例子,若是咱們衝着葉修的集卡去麥當勞消費,以下圖所示:瀏覽器

葉修集卡 麥當勞

實際流程都是須要一個「服務人員」,客戶點餐,付錢,服務人員提供食物和葉修卡,回到客戶手上。

若是從最大化利用角度而言,這裏的服務人員實際上是多餘的,客戶直接付錢拿貨更快捷,而這種直接請求的策略就是web請求的作法,客戶端發送請求,服務器返回數據,客戶端再顯示。

這麼多年下來,彷佛web的這種數據請求策略沒有任何問題,那爲什麼如今要在中間加一個「服務人員」-Service Worker呢?

主要是用戶應付一些特殊場景和需求,比方說離線處理(客官,這個賣完了),比方說消息推送(客官,你的漢堡好了……)等。而離線應用和消息推送正是目前native app相對於web app的優點所在。

因此,Service Worker出現的目的是讓web app能夠和native app開始真正意義上的競爭。

Service Worker概念和用法

咱們日常瀏覽器窗口中跑的頁面運行的是主JavaScript線程,DOM和window全局變量都是能夠訪問的。而Service Worker是走的另外的線程,能夠理解爲在瀏覽器背後默默運行的一個線程,脫離瀏覽器窗體,所以,window以及DOM都是不能訪問的,此時咱們可使用self訪問全局上下文,可參見這篇短文:「瞭解JS中的全局對象window.self和全局做用域self」。

因爲Service Worker走的是另外的線程,所以,就算這個線程翻江倒海也不會阻塞主JavaScript線程,也就是不會引發瀏覽器頁面加載的卡頓之類。同時,因爲Service Worker設計爲徹底異步,同步API(如XHRlocalStorage)不能在Service Worker中使用。

除了上面的些限制外,Service Worker對咱們的協議也有要求,就是必須是https協議的,但本地開發也弄個https協議是很麻煩的,好在還算人性化,Service Worker在http://localhost或者http://127.0.0.1這種本地環境下的時候也是能夠跑起來的。若是咱們想作個demo之類的頁面給其餘小夥伴看,咱們能夠藉助Github(由於是https協議的),例如我就專門建了個https-demo的小項目,專門用來放置一些須要https協議的demo頁面,相信離不開https的技術場景之後會愈來愈多。

最後,Service workers大量使用Promise,由於一般它們會等待響應後繼續,並根據響應返回一個成功或者失敗的操做,這些場景很是適合Promise。若是對Promise不是很瞭解,能夠先看我以前文章:「ES6 JavaScript Promise的感性認知」,而後再看:「JavaScript Promise迷你書(中文版)」。

Service Worker的生命週期

咱們直接看一個例子吧,以下HTML和JS代碼:

<h3>一些提示信息</h3>
<ul>
    <li>瀏覽器是否支持:<span id="isSupport"></span></li>
    <li>service worker是否註冊成功:<span id="isSuccess"></span></li>
    <li>當前註冊狀態:<span id="state"></span></li>
    <li>當前service worker狀態:<span id="swState"></span></li>
</ul>
<script src="./static/jquery.min.js"></script>
<script>
if ('serviceWorker' in navigator) {
    $('#isSupport').text('支持');

    // 開始註冊service workers
    navigator.serviceWorker.register('./sw-demo-cache.js', {
        scope: './'
    }).then(function (registration) {
        $('#isSuccess').text('註冊成功');

        var serviceWorker;
        if (registration.installing) {
            serviceWorker = registration.installing;
            $('#state').text('installing');
        } else if (registration.waiting) {
            serviceWorker = registration.waiting;
            $('#state').text('waiting');
        } else if (registration.active) {
            serviceWorker = registration.active;
            $('#state').text('active');
        }
        if (serviceWorker) {
            $('#swState').text(serviceWorker.state);
            serviceWorker.addEventListener('statechange', function (e) {
                $('#swState').append('&emsp;狀態變化爲' + e.target.state);
            });
        }
    }).catch (function (error) {
        $('#isSuccess').text('註冊沒有成功');
    });
} else {
    $('#isSupport').text('不支持');
}
</script>

代碼做用很簡單,判斷瀏覽器是否支持Service Worker以及記錄Service Worker的生命週期(當前狀態)。

在Chrome瀏覽器下,當咱們第一次訪問含有上面代碼的頁面時候,結果會是這樣:

會看到:installing → installed → activating → activated。

這個狀態變化過程實際上就是Service Worker生命週期的反應。

當咱們再次刷新此頁面,結果又會是這樣:
刷新頁面後的狀態記錄

直接顯示註冊成功狀態。

Service Worker註冊時候的生命週期是這樣的:

  1. Download – 下載註冊的JS文件
  2. Install – 安裝
  3. Activate – 激活

一旦安裝完成,如何註冊的JS沒有變化,則直接顯示當前激活態。

然而,實際的開發場景要更加複雜,使得Service Worker還有其它一些狀態。例以下圖這樣:

service worker更新後的狀態

出現了waiting,這是怎麼出現的呢?咱們修改了Service Worker註冊JS,而後重載的時候舊的Service Worker還在跑,新的Service Worker已經安裝等待激活。咱們打開開發者工具面板,Application → Service Workers,可能就會以下圖這樣:

waiting狀態控制面板截圖示意

此時,咱們頁面強刷下會變成這樣,進行了激活:

強刷後開始激活

再次刷新又回到註冊完畢狀態。

再次刷新後的狀態

而後,這些對應的狀態,Service Worker是有對應的事件名進行捕獲的,爲:

self.addEventListener('install', function(event) { /* 安裝後... */ });
self.addEventListener('activate', function(event) { /* 激活後... */ });

最後,Service Worker還支持fetch事件,來響應和攔截各類請求。

self.addEventListener('fetch', function(event) { /* 請求後... */ });

基本上,目前Service Worker的全部應用都是基於上面3個事件的,例如,本文要介紹的緩存和離線開發,'install'用來緩存文件,'activate'用來緩存更新,'fetch'用來攔截請求直接返回緩存數據。三者齊心,構成了完成的緩存控制結構。

Service Worker的兼容性

桌面端Chrome和Firefox可用,IE不可用。移動端Chrome可用,之後估計會快速支持。

3、瞭解Cache和CacheStorage

CacheCacheStorage都是Service Worker API下的接口,截圖以下:

cache和cacheStorage是Service Workers的接口

其中,Cache直接和請求打交道,CacheStorage和Cache對象打交道,咱們能夠直接使用全局的caches屬性訪問CacheStorage,例如,雖然API上顯示的是CacheStorage.open(),但咱們實際使用的時候,直接caches.open()就能夠了。

CacheCacheStorage的出現讓瀏覽器的緩存類型又多了一個:以前有memoryCache和diskCache,如今又多了個ServiceWorker cache。

至於CacheCacheStorage具體的增刪改查API直接去這裏一個一個找,Service Worker API的知識體量實在驚人,若想要系統學習,那可要作好充足的心理準備了。

可是,若是咱們只是但願在實際項目中應用一兩點實用技巧,則要輕鬆不少。例如,咱們但願在咱們的PC項目後者移動端頁面上漸進加強支持離線開發和更靈活的緩存控制,直接參照下面的套路便可!

4、藉助Service Worker和cacheStorage離線開發的固定套路

  1. 頁面上註冊一個Service Worker,例如:
    if ('serviceWorker' in navigator) {
        navigator.serviceWorker.register('./sw-demo-cache.js');
    }
  2. sw-demo-cache.js這個JS中複製以下代碼:
    var VERSION = 'v1';
    
    // 緩存
    self.addEventListener('install', function(event) {
      event.waitUntil(
        caches.open(VERSION).then(function(cache) {
          return cache.addAll([
            './start.html',
            './static/jquery.min.js',
            './static/mm1.jpg'
          ]);
        })
      );
    });
    
    // 緩存更新
    self.addEventListener('activate', function(event) {
      event.waitUntil(
        caches.keys().then(function(cacheNames) {
          return Promise.all(
            cacheNames.map(function(cacheName) {
              // 若是當前版本和緩存版本不一致
              if (cacheName !== VERSION) {
                return caches.delete(cacheName);
              }
            })
          );
        })
      );
    });
    
    // 捕獲請求並返回緩存數據
    self.addEventListener('fetch', function(event) {
      event.respondWith(caches.match(event.request).catch(function() {
        return fetch(event.request);
      }).then(function(response) {
        caches.open(VERSION).then(function(cache) {
          cache.put(event.request, response);
        });
        return response.clone();
      }).catch(function() {
        return caches.match('./static/mm1.jpg');
      }));
    });
  3. cache.addAll()方法中緩存文件數組換成你但願緩存的文件數組。

恭喜你,簡單3步曲,複製、粘貼、改路徑。你的網站已經支持Service Worker緩存,甚至離線也能夠自如訪問,支持各類網站,PC和Mobile通殺,不支持的瀏覽器沒有任何影響,支持的瀏覽器自然離線支持,想要更新緩存,v1換成v2就能夠,so easy!。

眼見爲實,您能夠狠狠地點擊這裏:藉助Service Worker和cacheStorage緩存及離線開發demo

進入頁面,咱們勾選network中的Offline,以下圖:
勾選Offline示意

結果刷新的時候,頁面依然正常加載,以下gif截屏:

離線後頁面刷新依舊正常顯示

咱們離線功能就此達成,出乎意料的簡單與實用。

5、和PWA技術的關係

PWA全稱爲「Progressive Web Apps」,漸進式網頁應用。功效顯著,收益明顯,以下圖:

PWA應用的收益數據

PWA的核心技術包括:

  • Web App Manifest – 在主屏幕添加app圖標,定義手機標題欄顏色之類
  • Service Worker – 緩存,離線開發,以及地理位置信息處理等
  • App Shell – 先顯示APP的主結構,再填充主數據,更快顯示更好體驗
  • Push Notification – 消息推送,以前有寫過「簡單瞭解HTML5中的Web Notification桌面通知

有此可見,Service Worker僅僅是PWA技術中的一部分,可是又獨立於PWA。也就是雖然PWA技術面向移動端,可是並不影響咱們在桌面端漸進使用Service Worker,考慮到本身廠子用戶70%~80%都是Chrome內核,收益或許會比預期的要高。

6、Service Worker更多的應用場景

Service Worker除了能夠緩存和離線開發,其能夠應用的場景還有不少,舉幾個例子(參考自MDN):

  • 後臺數據同步
  • 響應來自其它源的資源請求,
  • 集中接收計算成本高的數據更新,好比地理位置和陀螺儀信息,這樣多個頁面就能夠利用同一組數據
  • 在客戶端進行CoffeeScript,LESS,CJS/AMD等模塊編譯和依賴管理(用於開發目的)
  • 後臺服務鉤子
  • 自定義模板用於特定URL模式
  • 性能加強,好比預取用戶可能須要的資源,好比相冊中的後面數張圖片

開動本身的腦力,不少想法直接就能夠前端實現。

例如我想到了一個:重構人員寫高保真原型的時候,模擬請求僞造數據的時候,能夠不用依賴web環境,直接在Service Worker中攔截和返回模擬數據,因而整個項目只有乾淨的HTML、CSS和JS。

好了,就這麼多。

內容較多,行爲倉促,有錯誤在所不免,歡迎指正!

也歡迎交流,感謝閱讀!

相關文章
相關標籤/搜索