在2014年,W3C公佈了service worker的草案,service worker提供了不少新的能力,使得web app擁有與native app相同的離線體驗、消息推送體驗。 service worker是一段腳本,與web worker同樣,也是在後臺運行。做爲一個獨立的線程,運行環境與普通腳本不一樣,因此不能直接參與web交互行爲。native app能夠作到離線使用、消息推送、後臺自動更新,service worker的出現是正是爲了使得web app也能夠具備相似的能力。html
能夠:前端
後臺消息傳遞 網絡代理,轉發請求,僞造響應 離線緩存 消息推送 … … 本文以資源緩存爲例,說明一下service worker是如何工做的。html5
上圖是service worker生命週期,出處http://www.html5rocks.com/en/tutorials/service-worker/introduction/web
圖中能夠看到,一個service worker要經歷如下過程:ajax
激活成功以後,打開chrome://inspect/#service-workers能夠查看到當前運行的service workerchrome
監聽fetch和message事件,下面兩種事件會進行簡要描述json
是否銷燬由瀏覽器決定,若是一個service worker長期不使用或者機器內存有限,則可能會銷燬這個worker;後端
在頁面發起http請求時,service worker能夠經過fetch事件攔截請求,而且給出本身的響應。 w3c提供了一個新的fetch api,用於取代XMLHttpRequest,與XMLHttpRequest最大不一樣有兩點:api
fetch()方法返回的是Promise對象,經過then方法進行連續調用,減小嵌套。ES6的Promise在成爲標準以後,會愈來愈方便開發人員。數組
提供了Request、Response對象,若是作事後端開發,對Request、Response應該比較熟悉。前端要發起請求能夠經過url發起,也可使用Request對象發起,並且Request能夠複用。可是Response用在哪裏呢?在service worker出現以前,前端確實不會本身給本身發消息,可是有了service worker,就能夠在攔截請求以後根據須要發回本身的響應,對頁面而言,這個普通的請求結果並無區別,這是Response的一處應用。
下面是在http://www.sitepoint.com/introduction-to-the-fetch-api/中,做者利用fetch api經過fliker的公開api獲取圖片的例子,註釋中詳細解釋了每一步的做用:
/* 因爲是get請求,直接把參數做爲query string傳遞了 */ var URL = 'https://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=your_api_key&format=json&nojsoncallback=1&tags=penguins'; function fetchDemo() { // fetch(url, option)支持兩個參數,option中能夠設置header、body、method信息 fetch(URL).then(function(response) { // 經過promise 對象得到相應內容,而且將響應內容按照json格式轉成對象,json()方法調用以後返回的依然是promise對象 // 也能夠把內容轉化成arraybuffer、blob對象 return response.json(); }).then(function(json) { // 渲染頁面 insertPhotos(json); }); } fetchDemo();
fetch api與XMLHttpRequest相比,更加簡潔,而且提供的功能更全面,資源獲取方式比ajax更優雅。兼容性方面:chrome 42開始支持,對於舊瀏覽器,能夠經過官方維護的polyfill支持。
頁面和serviceWorker之間能夠經過posetMessage()方法發送消息,發送的消息能夠經過message事件接收到。
這是一個雙向的過程,頁面能夠發消息給service worker,service worker也能夠發送消息給頁面,因爲這個特性,能夠將service worker做爲中間紐帶,使得一個域名或者子域名下的多個頁面能夠自由通訊。
這裏是一個小的頁面之間通訊demohttps://nzv3tos3n.qnssl.com/message/msg-demo.html
下面介紹一個利用service worker緩存離線文件的例子 準備index.js,用於註冊service-worker
if (navigator.serviceWorker) { navigator.serviceWorker.register('service-worker.js').then(function(registration) { console.log('service worker 註冊成功'); }).catch(function (err) { console.log('servcie worker 註冊失敗') }); }
在上述代碼中,註冊了service-worker.js做爲當前路徑下的service worker。因爲service worker的權限很高,全部的代碼都須要是安全可靠的,因此只有https站點纔可使用service worker,固然localhost是一個特例。 註冊完畢,如今開始寫service-worker.js代碼。 根據前面的生命週期圖,在一個新的service worker被註冊之後,首先會觸發install事件,在service-workder.js中,能夠經過監聽install事件進行一些初始化工做,或者什麼也不作。 由於咱們是要緩存離線文件,因此能夠在install事件中開始緩存,可是隻是將文件加到caches緩存中,真正想讓瀏覽器使用緩存文件須要在fetch事件中攔截
var cacheFiles = [ 'about.js', 'blog.js' ]; self.addEventListener('install', function (evt) { evt.waitUntil( caches.open('my-test-cahce-v1').then(function (cache) { return cache.addAll(cacheFiles); }) ); });
首先定義了須要緩存的文件數組cacheFile,而後在install事件中,緩存這些文件。 evt是一個InstallEvent對象,繼承自ExtendableEvent,其中的waitUntil()方法接收一個promise對象,直到這個promise對象成功resolve以後,纔會繼續運行service-worker.js。 caches是一個CacheStorage對象,使用open()方法打開一個緩存,緩存經過名稱進行區分。 得到cache實例以後,調用addAll()方法緩存文件。
這樣就將文件添加到caches緩存中了,想讓瀏覽器使用緩存,還須要攔截fetch事件
// 緩存圖片 self.addEventListener('fetch', function (evt) { evt.respondWith( caches.match(evt.request).then(function(response) { if (response) { return response; } var request = evt.request.clone(); return fetch(request).then(function (response) { if (!response && response.status !== 200 && !response.headers.get('Content-type').match(/image/)) { return response; } var responseClone = response.clone(); caches.open('my-test-cache-v1').then(function (cache) { cache.put(evt.request, responseClone); }); return response; }); }) ) });
經過監聽fetch事件,service worker能夠返回本身的響應。
首先檢緩存中是否已經緩存了這個請求,若是有,就直接返回響應,就減小了一次網絡請求。不然由service workder發起請求,這時的service workder起到了一箇中間代理的做用。
service worker請求的過程經過fetch api完成,獲得response對象之後進行過濾,查看是不是圖片文件,若是不是,就直接返回請求,不會緩存。
若是是圖片,要先複製一份response,緣由是request或者response對象屬於stream,只能使用一次,以後一份存入緩存,另外一份發送給頁面。 這就是service worker的強大之處:攔截請求,僞造響應。fetch api在這裏也起到了很大的做用。
service worker的更新很簡單,只要service-worker.js的文件內容有更新,就會使用新的腳本。可是有一點要注意:舊緩存文件的清除、新文件的緩存要在activate事件中進行,由於可能舊的頁面還在使用以前的緩存文件,清除以後會失去做用。
在初次使用service worker的過程當中,也遇到了一些問題,下面是其中兩個
service worker並非一直在後臺運行的。在頁面關閉後,瀏覽器能夠繼續保持service worker運行,也能夠關閉service worker,這取決與瀏覽器本身的行爲。因此不要定義一些全局變量,例以下面的代碼(來自https://jakearchibald.com/2014/service-worker-first-draft/):
var hitCounter = 0; this.addEventListener('fetch', function(event) { hitCounter++; event.respondWith( new Response('Hit number ' + hitCounter) ); });
返回的結果多是沒有規律的:1,2,1,2,1,1,2….,緣由是hitCounter並無一直存在,若是瀏覽器關閉了它,下次啓動的時候hitCounter就賦值爲0了 這樣的事情致使調試代碼困難,當你更新一個service worker之後,只有在打開新頁面之後纔可能使用新的service worker,在調試過程當中常常等上一兩分鐘纔會使用新的,比較抓狂。
當service worker監聽fetch事件之後,對應的請求都會通過service worker。經過chrome的network工具,能夠看到此類請求會標註:from service worker。若是service worker中出現了問題,會致使全部請求失敗,包括普通的html文件。因此service worker的代碼質量、容錯性必定要很好才能保證web app正常運行。