借助Service Worker和cacheStorage緩存及離線開發 (轉載)
一、緩存和離線開發
說得HTML5離線開發,我們通常第一反應是使用html5 manifest緩存技術,此技術已經出現很多年了,我以前多次了解過,也見過一些實踐案例,但是卻從未在博客中介紹過,因為並不看好。
為什麽不看好呢?用一句話解釋就是“投入產出比有些低”。
對於web應用,掉線不能使用是理所當然的,絕不會有哪個開發人員會因為網頁在沒網的時候打不開被測試MM提bug,或者被用戶投訴,所以,我們的web頁面不支持離線完全不會有什麽影響。但如果我們希望支持離線,會發現,我投入的精力和成本啊還真不小,就說一點,html5 manifest緩存技術需要服務端配合直接就讓成本蹭蹭蹭的上去了,因為已經不僅僅是前端這一個工種的工作了,很可能就需要跨團隊協作,這事情立馬一下子就啰嗦了,開發時候啰嗦,以後維護也啰嗦。
而支持離線的收益,我估算了下,比支持無障礙訪問收益還要低。所以,站在商業的角度講,如果一項技術成本比較高,收益比較小,是並不建議實際應用的。開發人員喜歡在重要的項目中玩些時髦的酷酷的新技術自嗨是可以理解的,但前提是收益明顯。如果只是自嗨,留下爛攤,有必要警惕了,業務意識和職業素養說明還有待提高。
鋪墊這麽多,就是要轉折說下本文要介紹的緩存技術。
本文所要介紹的基於Service Worker和cacheStorage緩存及離線開發,套路非常固定,無侵入,且語言純正,直接復制粘貼就可以實現緩存和離線功能,純前端,無需服務器配合。一個看上去很酷的功能只要復制粘貼就可以實現,絕對是成本極低的,小白中的小白也能上手。
由於成本幾乎可以忽略不計,此時離線功能支持的投入產出比相當高了,經驗告訴我這種技術以後流行和普及的可能性非常高,於是迫不及待分享給大家。
在開始案例前,我們有必要先了解幾個概念。
二、通俗易懂的方式介紹Service Worker
Service Worker直白翻譯就是“服務人員”,看似不經意的翻譯,實際上卻正道出了Service Worker的精髓所在。
舉個例子,如果我們沖著葉修的集卡去麥當勞消費,如下圖所示:
實際流程都是需要一個“服務人員”,客戶點餐,付錢,服務人員提供食物和葉修卡,回到客戶手上。
如果從最大化利用角度而言,這裏的服務人員其實是多余的,客戶直接付錢拿貨更快捷,而這種直接請求的策略就是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(如XHR
和localStorage
)不能在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(‘ 狀態變化為‘ + 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註冊時候的生命周期是這樣的:
- Download – 下載註冊的JS文件
- Install – 安裝
- Activate – 激活
一旦安裝完成,如何註冊的JS沒有變化,則直接顯示當前激活態。
然而,實際的開發場景要更加復雜,使得Service Worker還有其它一些狀態。例如下圖這樣:
出現了waiting
,這是怎麽出現的呢?我們修改了Service Worker註冊JS,然後重載的時候舊的Service Worker還在跑,新的Service Worker已經安裝等待激活。我們打開開發者工具面板,Application → Service Workers,可能就會如下圖這樣:
此時,我們頁面強刷下會變成這樣,進行了激活:
再次刷新又回到註冊完畢狀態。
然後,這些對應的狀態,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可用,以後估計會快速支持。
三、了解Cache和CacheStorage
Cache
和CacheStorage
都是Service Worker API下的接口,截圖如下:
其中,Cache
直接和請求打交道,CacheStorage
和Cache對象打交道,我們可以直接使用全局的caches
屬性訪問CacheStorage,例如,雖然API上顯示的是CacheStorage.open()
,但我們實際使用的時候,直接caches.open()
就可以了。
Cache
和CacheStorage
的出現讓瀏覽器的緩存類型又多了一個:之前有memoryCache和diskCache,現在又多了個ServiceWorker cache。
至於Cache
和CacheStorage
具體的增刪改查API直接去這裏一個一個找,Service Worker API的知識體量實在驚人,若想要系統學習,那可要做好充足的心理準備了。
但是,如果我們只是希望在實際項目中應用一兩點實用技巧,則要輕松很多。例如,我們希望在我們的PC項目後者移動端頁面上漸進增強支持離線開發和更靈活的緩存控制,直接參照下面的套路即可!
四、借助Service Worker和cacheStorage離線開發的固定套路
- 頁面上註冊一個Service Worker,例如:
if (‘serviceWorker‘ in navigator) { navigator.serviceWorker.register(‘./sw-demo-cache.js‘); }
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‘); })); });
- 把
cache.addAll()
方法中緩存文件數組換成你希望緩存的文件數組。
恭喜你,簡單3步曲,復制、粘貼、改路徑。你的網站已經支持Service Worker緩存,甚至離線也可以自如訪問,支持各種網站,PC和Mobile通殺,不支持的瀏覽器沒有任何影響,支持的瀏覽器天然離線支持,想要更新緩存,v1
換成v2
就可以,so easy!。
眼見為實,您可以狠狠地點擊這裏:借助Service Worker和cacheStorage緩存及離線開發demo
進入頁面,我們勾選network中的Offline,如下圖:
結果刷新的時候,頁面依然正常加載,如下gif截屏:
我們離線功能就此達成,出乎意料的簡單與實用。
五、和PWA技術的關系
PWA全稱為“Progressive Web Apps”,漸進式網頁應用。功效顯著,收益明顯,如下圖:
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內核,收益或許會比預期的要高。
六、Service Worker更多的應用場景
Service Worker除了可以緩存和離線開發,其可以應用的場景還有很多,舉幾個例子(參考自MDN):
- 後臺數據同步
- 響應來自其它源的資源請求,
- 集中接收計算成本高的數據更新,比如地理位置和陀螺儀信息,這樣多個頁面就可以利用同一組數據
- 在客戶端進行CoffeeScript,LESS,CJS/AMD等模塊編譯和依賴管理(用於開發目的)
- 後臺服務鉤子
- 自定義模板用於特定URL模式
- 性能增強,比如預取用戶可能需要的資源,比如相冊中的後面數張圖片
開動自己的腦力,很多想法直接就可以前端實現。
例如我想到了一個:重構人員寫高保真原型的時候,模擬請求偽造數據的時候,可以不用依賴web環境,直接在Service Worker中攔截和返回模擬數據,於是整個項目只有幹凈的HTML、CSS和JS。
好了,就這麽多。
內容較多,行為倉促,有錯誤在所難免,歡迎指正!
也歡迎交流,感謝閱讀!
借助Service Worker和cacheStorage緩存及離線開發 (轉載)