Service Worker的由來
W3C 組織早在 2014 年 5 月就提出過 Service Worker 這樣的一個 HTML5 API 闭树,主要用來做持久的離線緩存琐旁。service worker是瀏覽器的一個高級特性,本質是一個web worker窿锉,是獨立于網頁運行的腳本
枢赔。 web worker這個api被造出來時倦始,就是為了解放主線程
王浴。因為脆炎,瀏覽器中的JavaScript都是運行在單一個線程上,隨著web業(yè)務變得越來越復雜氓辣,js中耗時間秒裕、耗資源的運算過程則會導致各種程度的性能問題。 而web worker由于獨立于主線程钞啸,則可以將一些復雜的邏輯交由它來去做几蜻,完成后再通過postMessage的方法告訴主線程。 service worker則是web worker的升級版本体斩,相較于后者入蛆,前者擁有了持久離線緩存的能力。
Service Worker的特點
- 獨立于主線程硕勿、在后臺運行的腳本
- 被install后就永遠存在,除非被手動卸載
- 必須是https的協議才能使用枫甲。不過在本地調試時源武,在
http://localhost
和http://127.0.0.1
下也是可以跑起來的。 - 不能直接操縱dom:因為sw是個獨立于網頁運行的腳本想幻。
- 可攔截請求和返回粱栖,緩存文件。sw可以通過fetch這個api脏毯,來攔截網絡和處理網絡請求闹究,再配合cacheStorage來實現web頁面的緩存管理以及與前端postMessage通信。
Service Worker的兼容性
下圖是Service worker現有的瀏覽器支持版本食店, 從圖上可以看出火狐和谷歌的支持是比較良好的渣淤,IE和safari需要相對比較高的版本才能夠支持赏寇。移動端的話ios也需要從ios13才開始支持在安卓上的支持會相對廣泛一點。
Service Worker的工作流程
- Service Worker 文件只在首次注冊的時候執(zhí)行了一次价认。
- 安裝嗅定、激活流程也只是在首次執(zhí)行 Service Worker 文件的時候進行了一次。
- 首次注冊成功的 Service Worker 不能攔截當前頁面的請求用踩。
- 非首次注冊的 Service Worker 可以控制當前的頁面并能攔截請求
- Service Worker 首次注冊或者有新版本觸發(fā)更新的時候渠退,才會重新創(chuàng)建一個 worker 工作線程并解析執(zhí)行 Service Worker 文件,在這之后并進入 Service Worker 的安裝和激活生命周期
Service Worker的生命周期
當一個servicework被注冊成功后脐彩,它將開始它的生命周期碎乃,我們對servicework的操作一般都是在其生命周期里面進行的。servicework的生命周期分為這么幾個狀態(tài) 安裝中, 安裝后, 激活中, 激活后, 廢棄惠奸。
安裝( installing ):這個狀態(tài)發(fā)生在 Service Worker 注冊之后梅誓,表示開始安裝,這個狀態(tài)會觸發(fā) install 事件晨川,一般會在install事件的回調里面進行靜態(tài)資源的離線緩存证九, 如果這些靜態(tài)資源緩存失敗了,那 Service Worker 安裝就會失敗共虑,生命周期終止愧怜。
安裝后( installed ):當成功捕獲緩存到的資源時,servicework會變?yōu)檫@個狀態(tài)妈拌,當此時沒有其他的servicework線程在工作時拥坛,會立即進入激活狀態(tài),如果此時有正在工作的servicework工作線程尘分,則會等待其他的 Service Worker 線程被關閉后才會被激活猜惋。可以使用 self.skipWaiting() 方法強制正在等待的servicework工作線程進入激活狀態(tài)培愁。
激活( activating ):在這個狀態(tài)下會觸發(fā)activate事件著摔,在activate 事件的回調中去清理舊版緩存。
激活后( activated ):在這個狀態(tài)下定续,servicework會取得對整個頁面的控制
廢棄狀態(tài) ( redundant ):這個狀態(tài)表示一個 Service Worker 的生命周期結束谍咆。新版本的 Service Worker 替換了舊版本的 Service Worker會出現這個狀態(tài)
更新Service Worker
更新一個servicework,最直接的辦法就是修改servicework.js這個文件私股,當刷新瀏覽器時摹察,瀏覽器嘗試重新下載servicework.js腳本文件,然后會與之前的版本比對倡鲸,一旦發(fā)現文件內容不一致供嚎,就會進入更新流程。
新的 servicework 被啟動安裝并觸發(fā) install事件。
安裝成功后克滴,新版 servicework 進入等待狀態(tài)逼争,此時頁面的控制權還在老版 servicework手中。
當servicework控制的所有終端都關閉之后偿曙,或者手動self.skipWaiting()氮凝,舊的 servicework 才能被終止,此時新的servicework被激活望忆,觸發(fā)activate 事件罩阵。
用戶再次訪問頁面,或刷新頁面启摄,新的 service work 啟動控制頁面稿壁。
Service Worker 的簡單實踐
- 注冊。 serviceWorker對象存在于navigator對象下歉备,可以再主線程中調用
navigator.serviceWorker.register()
方法來注冊servicework,register 方法接受兩個參數,第一個參數表示servicework.js相對于origin的路徑傅是,第二個參數是 Serivce Worker 的配置項,可選填蕾羊,其中比較重要的是 scope 屬性喧笔,用來指定你想讓 service worker 控制的內容的目錄。 默認值為servicework.js所在的目錄龟再。這個屬性所表示的路徑不能在 service worker 文件的路徑之上书闸,默認是 Serivce Worker 文件所在的目錄。 成功注冊或返回一個promise利凑。
// 頁面的入口文件
if (navigator.serviceWorker) {
window.addEventListener('load', () => {
console.log('開始注冊ServiceWorker')
navigator.serviceWorker
.register('./serviceworker.js')
.then((reg) => {
console.log('ServiceWorker register success: ', reg)
})
.catch((err) => {
console.log('ServiceWorker register failed: ', err)
})
})
}
- 安裝
self.addEventListener('install', (event) => {
console.log('install事件')
self.skipWaiting() //用來強制更新的servicework跳過等待時間
event.waitUntil(
caches.open(CACHE_NAME).then(function (cache) {
return cache.addAll(urlsToCache)
})
)
})
首先 self.skipWaiting()
執(zhí)行浆劲,告知瀏覽器直接跳過等待階段,淘汰過期的Service Worker腳本哀澈,直接開始嘗試激活新的Service Worker牌借。然后使用 caches.open
打開一個Cache,打開后割按,通過cache.addAll
嘗試緩存我們預先聲明的文件膨报。 CacheStorage 全局的cache Api 并非只有在sw中才能用 瀏覽器控制臺直接用也是可以的,所以是掛在window下的
event.waitUntil()
只能在 Service Worker 的 install 或者 activate 事件中使用适荣;看起來像是一個 callback丙躏,用來延長事件的作用時間,但是,即便你不使用它束凑,程序也可能正常運行。如果你傳遞了一個 Promise 給它栅盲,那么只有當該 Promise resolved 時汪诉,Service Worker 才會完成 install;如果 Promise rejected 掉,那么整個 Service Worker 便會被廢棄掉扒寄。因此鱼鼓,cache.addAll 里面,只要有一個資源獲取失敗该编,整個 Service Worker 便會失效迄本。
在 install 事件回調被調用時,它把即將被激活的 worker 線程狀態(tài)延遲為 installing 狀態(tài)课竣,直到傳遞的 Promise 被成功地 resolve嘉赎。這主要用于確保:Service Worker 工作線程在所有依賴的核心 cache 被緩存之前都不會被激活。
在 activate 事件回調被調用時于樟,它把即將被激活的 worker 線程狀態(tài)延遲為 activating 狀態(tài)公条,直到傳遞的 Promise 被成功地 resolve。這主要用于確保:任何功能事件不會被分派到 ServiceWorkerGlobalScope 對象迂曲,直到它刪除過期的緩存條目靶橱。
當 waitUntil()運行時,如果 Promise 是 rejected 那么installing 或者 activating 的狀態(tài)會被設置為 redundant路捧。
- 激活
self.addEventListener('activate', (event) => {
console.log('activate事件')
var cacheWhitelist = [CACHE_NAME]
self.clients.claim() // 保證 激活之后能夠馬上作用于所有的終端
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName)
}
})
)
})
)
})
在激活servicework時需要刪除之前的緩存关霸,可將需要的緩存放在有個白名單中,然后通過caches.keys()拿到所有緩存杰扫,將不再白名單中的緩存刪掉队寇。
- 攔截網絡請求
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
if (response)
return response;
}
return fetch(event.request);
})
);
});
通過監(jiān)聽servicework的 fetch 事件來攔截網絡請求,調用 event 上的 respondWith() 方法來劫持當前servicework控制域下的 HTTP 請求涉波,該方法會直接返回一個Promise 結果 英上,這個結果就會是http請求的響應。上面代碼中就一個簡單的邏輯啤覆,先劫持http請求苍日,然后看看緩存中是否有這個請求的資源,如果有則直接返回窗声,如果沒有就去請求服務器上的資源相恃。 event.respondWith 方法只能在 Service Worker 的 fetch 事件中使用。
Cache Stroage 只能緩存靜態(tài)資源笨觅,所以它只能緩存用戶的 GET 請求拦耐;Cache Stroage 中的緩存不會過期,但是瀏覽器對它的大小是有限制的见剩,所以需要我們定期進行清理杀糯。
對應post 請求我們也可以通過 fetch方法攔截到,來進行一些自定義的返回苍苞。
- service worker 與主線程之間的通信
- 主線程
// 傳遞
navigator.serviceWorker.controller && navigator.serviceWorker.controller.postMessage("this message is from page");
// 接收
navigator.serviceWorker.addEventListener('message', function (e) {
console.log('service worker傳遞的信息',e.data);
});
- service worker
self.addEventListener('message', (event)=>{
console.log('頁面?zhèn)鬟f過來的數據',event.data) // 收到主線程傳遞的信息
event.source.postMessage('this message is from sw.js to page'); // 向主線程傳遞信息
})
- service worker 卸載
navigator.serviceWorker.getRegistrations().then(function (registrations) {
for (let registration of registrations) {
//安裝在網頁的service worker不止一個固翰,找到我們的那個并刪除
console.log(registration)
if (registration && registration.scope === 'http://localhost:8080/') {
registration.unregister()
}
}
})
- serviceworker 和 http緩存
- 在請求速度方面:
-
在緩存文件更新方面
如果是http緩存狼纬,一刷新頁面就可以拿到最新的資源
如果是sw緩存, 一刷新頁面骂际,會返回當前緩存中的資源(不是最新)疗琉,然后請求sw.js文件發(fā)現更新后重新進入sw生命周期,重新去更新緩存歉铝,當你再次刷新時才能拿到最新資源盈简。所以在緩存資源更新時,sw會延遲一次刷新才能獲取最新資源
-
在緩存控制方面:
http一般是由服務器端控制的太示,而sw則是可以前端自己控制柠贤,可以更好地控制緩存。
協商緩存返回狀態(tài)碼304先匪, 強緩存返回的是200种吸, 在這點上server worker和強緩存比較類似的返回也是200, 會對請求進行攔截呀非,不會真是的發(fā)出請求坚俗。
其他
會有一些開源的框架對 service worker進行了一些封裝,避免了我們重復繁瑣的去寫一下fetch監(jiān)聽岸裙,install事件猖败,active事件等,大大簡化了繁瑣的寫法降允。關注度比較高的應該是谷歌推出的 workbox, 圍繞他也有一系列的工具恩闻,如 workbox-cli、gulp-workbox剧董、webpack-workbox-plagin
等等幢尚。
workbox提供了一下幾種緩存策略:
Stale-While-Revalidate 當請求的路由有對應的 Cache 緩存結果就直接返回,在返回 Cache 緩存結果的同時會在后臺發(fā)起網絡請求拿到請求結果并更新 Cache 緩存翅楼,如果本來就沒有 Cache 緩存的話尉剩,直接就發(fā)起網絡請求并返回結果 ( 從緩存中讀取資源的同時發(fā)送網絡請求更新本地緩存 )
CacheFirst 當匹配到請求之后直接從 Cache 緩存中取得結果,如果 Cache 緩存中沒有結果毅臊,那就會發(fā)起網絡請求理茎,拿到網絡請求結果并將結果更新至 Cache 緩存,并將結果返回給客戶端管嬉。這種策略比較適合結果不怎么變動且對實時性要求不高的請求皂林。 (有緩存用緩存,無緩存則請求網絡)
CacheOnly 這個策略也比較直接蚯撩,直接使用 Cache 緩存的結果础倍,并將結果返回給客戶端,這種策略比較適合一上線就不會變的靜態(tài)資源請求胎挎。 (僅使用緩存)
NetworkFirst 采用網絡優(yōu)先的策略沟启,也就是優(yōu)先嘗試拿到網絡請求的返回結果扰楼,如果拿到網絡請求的結果,就將結果返回給客戶端并且寫入 Cache 緩存美浦,如果網絡請求失敗,那最后被緩存的 Cache 緩存結果就會被返回到客戶端项栏,這種策略一般適用于返回結果不太固定或對實時性有要求的請求浦辨,為網絡請求失敗進行兜底。 (有網的情況下采取網絡沼沈,沒網的情況下用緩存)
NetworkOnly 比較直接的策略流酬,直接強制使用正常的網絡請求,并將結果返回給客戶端列另,這種策略比較適合對實時性要求非常高的請求芽腾。 (僅使用網絡請求)
總結
本次只是對于service worker 的一些基礎API的嘗試和學習,可以當做是一些學習資料的整理页衙, 希望有不對的地方可以指出摊滔,service worker的知識點還有很多,有比較深入了解的同學也歡迎補充