說得HTML5離線開發,咱們一般第一反應是使用html5 manifest緩存技術,此技術已經出現不少年了,我之前屢次瞭解過,也見過一些實踐案例,可是卻從未在博客中介紹過,由於並不看好。html
爲何不看好呢?用一句話解釋就是「投入產出比有些低」。前端
對於web應用,掉線不能使用是理所固然的,毫不會有哪一個開發人員會由於網頁在沒網的時候打不開被測試MM提bug,或者被用戶投訴,因此,咱們的web頁面不支持離線徹底不會有什麼影響。但若是咱們但願支持離線,會發現,我投入的精力和成本啊還真不小,就說一點,html5 manifest緩存技術須要服務端配合直接就讓成本蹭蹭蹭的上去了,由於已經不只僅是前端這一個工種的工做了,極可能就須要跨團隊協做,這事情立馬一會兒就囉嗦了,開發時候囉嗦,之後維護也囉嗦。html5
而支持離線的收益,我估算了下,比支持無障礙訪問收益還要低。因此,站在商業的角度講,若是一項技術成本比較高,收益比較小,是並不建議實際應用的。開發人員喜歡在重要的項目中玩些時髦的酷酷的新技術自嗨是能夠理解的,但前提是收益明顯。若是隻是自嗨,留下爛攤,有必要警戒了,業務意識和職業素養說明還有待提升。jquery
鋪墊這麼多,就是要轉折說下本文要介紹的緩存技術。git
本文所要介紹的基於Service Worker和cacheStorage緩存及離線開發,套路很是固定,無侵入,且語言純正,直接複製粘貼就能夠實現緩存和離線功能,純前端,無需服務器配合。一個看上去很酷的功能只要複製粘貼就能夠實現,絕對是成本極低的,小白中的小白也能上手。github
因爲成本幾乎能夠忽略不計,此時離線功能支持的投入產出比至關高了,經驗告訴我這種技術之後流行和普及的可能性很是高,因而火燒眉毛分享給你們。web
在開始案例前,咱們有必要先了解幾個概念。數組
Service Worker直白翻譯就是「服務人員」,看似不經意的翻譯,實際上卻正道出了Service Worker的精髓所在。promise
舉個例子,若是咱們衝着葉修的集卡去麥當勞消費,以下圖所示:瀏覽器
實際流程都是須要一個「服務人員」,客戶點餐,付錢,服務人員提供食物和葉修卡,回到客戶手上。
若是從最大化利用角度而言,這裏的服務人員實際上是多餘的,客戶直接付錢拿貨更快捷,而這種直接請求的策略就是web請求的作法,客戶端發送請求,服務器返回數據,客戶端再顯示。
這麼多年下來,彷佛web的這種數據請求策略沒有任何問題,那爲什麼如今要在中間加一個「服務人員」-Service Worker呢?
主要是用戶應付一些特殊場景和需求,比方說離線處理(客官,這個賣完了),比方說消息推送(客官,你的漢堡好了……)等。而離線應用和消息推送正是目前native app相對於web app的優點所在。
因此,Service Worker出現的目的是讓web app能夠和native app開始真正意義上的競爭。
咱們日常瀏覽器窗口中跑的頁面運行的是主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迷你書(中文版)」。
咱們直接看一個例子吧,以下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註冊時候的生命週期是這樣的:
一旦安裝完成,如何註冊的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'
用來攔截請求直接返回緩存數據。三者齊心,構成了完成的緩存控制結構。
桌面端Chrome和Firefox可用,IE不可用。移動端Chrome可用,之後估計會快速支持。
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項目後者移動端頁面上漸進加強支持離線開發和更靈活的緩存控制,直接參照下面的套路便可!
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全稱爲「Progressive Web Apps」,漸進式網頁應用。功效顯著,收益明顯,以下圖:
PWA的核心技術包括:
有此可見,Service Worker僅僅是PWA技術中的一部分,可是又獨立於PWA。也就是雖然PWA技術面向移動端,可是並不影響咱們在桌面端漸進使用Service Worker,考慮到本身廠子用戶70%~80%都是Chrome內核,收益或許會比預期的要高。
Service Worker除了能夠緩存和離線開發,其能夠應用的場景還有不少,舉幾個例子(參考自MDN):
開動本身的腦力,不少想法直接就能夠前端實現。
例如我想到了一個:重構人員寫高保真原型的時候,模擬請求僞造數據的時候,能夠不用依賴web環境,直接在Service Worker中攔截和返回模擬數據,因而整個項目只有乾淨的HTML、CSS和JS。
好了,就這麼多。
內容較多,行爲倉促,有錯誤在所不免,歡迎指正!
也歡迎交流,感謝閱讀!