Service Worker是什么
從功能上來說奕污,Service Worker
是一種提供離線緩存控制功能的一種Worker
千诬,同時,也具有消息推送和后臺同步的功能抗楔,可以通過Service Worker來緩存網(wǎng)頁的資源俊扭,然后攔截Fetch請求來執(zhí)行相應的緩存處理操作队橙。因為是一種Worker,所以Service Worker也具有Worker的一些基本特性萨惑,例如:
- 獨立于主線程運行
- 不能訪問
window
對象捐康,但是擁有自身的一個執(zhí)行上下文,例如Service Worker
的ServiceWorkerGlobalScope
- 具有消息api來和頁面進行消息交互
Service Worker是一種共享型Worker庸蔼,它不同于專用型Worker只能在創(chuàng)建它的頁面中使用解总,默認配置下,service Worker可以被當前注冊腳本的域名下所有頁面公用姐仅。
也就是說倾鲫,只要在一個根域名注冊一個ServiceWorker,那么所有這個域名下的頁面都會收到影響萍嬉。
Service Worker使用
要使用ServiceWorker乌昔,首先,需要通過serviceWorkerContainer.register()
來進行注冊壤追,例如:
ServiceWorkerContainer.register("/test/service.js", {scope:"./"})
.then(
function(ServiceWorkerRegistration) {
// do something
}
);
假設當前域名為www.baidu.com/serviceWork
磕道,那么上面的方法就在www.baidu.com/serviceWork/test
下面注冊了一個ServiceWorker,假如把scope改成./hahaha行冰,那么作用域就變成了www.baidu.com/serviceWork/test/hahaha
溺蕉,只要處于這個域名之下的所有頁面,都受到這個ServiceWorker的控制悼做。但是疯特,假如將scope改成"../",頁面就會報出一個錯誤肛走,因為這時候不允許設置比service.js所在位置層級更高的路徑漓雅,除非添加一個Service-Worker-Allowed
header。
注冊完成之后,受控界面會去安裝serviceWorker邻吞,安裝完成之后會處于等待狀態(tài)组题,接下來有可能會進入激活狀態(tài),激活狀態(tài)之后抱冷,頁面還不一定是受控的崔列,不過有相應的api可以控制這一系列流程。這些流程就是ServiceWorker最最復雜的生命周期了旺遮。
ServiceWorker生命周期
serviceWorker的生命周期有點復雜赵讯,情況很多,但是基本上符合一個理念耿眉,那就是漸進式边翼。
install
執(zhí)行完注冊之后,瀏覽器會去下載跷敬,假如腳本沒有錯誤的話讯私,就會進行安裝热押,安裝會在Worker內(nèi)觸發(fā)install事件西傀,安裝分為兩個狀態(tài):installing和installed,這里可以執(zhí)行一些操作桶癣。
self.addEventListener("install",installEvent=>{
self.skipWaiting();//跳過waiting
installEvent.waitUntil(
caches.open(CACHE_NAME).then(cache=>{
return cache.add("http://upload.jianshu.io/users/upload_avatars/9545112/f186bd4a-1da4-4912-b926-a744bc128d06.png?imageMogr2/auto-orient/strip|imageView2/1/w/240/h/240");
})
)
});
這里會有一個installEvent拥褂,通過waitUtil方法可以阻塞安裝狀態(tài),這個方法可以傳入一個promise牙寞,只有這個promise正確resolve之后饺鹃,才會完成安裝,同時间雀,還有一個skipwaiting方法悔详,這個方法可以跳過installed和activating狀態(tài)之間的waiting狀態(tài)。
首次打開頁面會安裝work惹挟,然后接下來再檢測到新的work也會再次執(zhí)行安裝茄螃。
觸發(fā)更新的幾種情況:
- 第一次導航到作用域范圍內(nèi)頁面的時候
- 當在24小時內(nèi)沒有進行更新檢測并且觸發(fā)功能性時間如push或sync的時候
- SW 的 URL 發(fā)生變化并調(diào)用.register()時
- 手動執(zhí)行reg.update()
- 重載頁面(有些情況下不會更新,原因不明)
更新會比對舊版本和新版本的字節(jié)连锯,假如字節(jié)不一致归苍,則更新。在更新install完成之后运怖,會進入waiting狀態(tài)拼弃,直到舊版本不控制任何client(受控的頁面)再進入activating狀態(tài)。
activate
activate的狀態(tài)比測試妹子的思考邏輯還難猜測摇展。但是這個遵循一個原則吻氧,只有所有的頁面都不受老的serviceWorker控制的時候,才會開始激活。意思就是所有worker作用的頁面都關(guān)了医男,相當于重啟更新砸狞,再次啟動的時候,才會進入激活狀態(tài)镀梭。激活也會觸發(fā)一個activate狀態(tài).
self.addEventListener("activate", ExtendableEvent => {
ExtendableEvent.waitUntil(self.clients.claim());
}
activate也和install一樣刀森,也有waitUtil方法,效果也是一樣的报账。激活完成之后其實還不一定有效果研底,因為頁面可能還處于不受控的狀態(tài),或者有另外一個頁面透罢,沒有刷新的榜晦,但是這個時候新開一個頁面更新了work,這個時候就需要調(diào)用clients.claim操作來讓所有打開的頁面受控羽圃。但是...work的執(zhí)行是異步的乾胶,也就是說,頁面在執(zhí)行這個方法之前是不受控朽寞,或者是不是受最新的work控制的识窿,在利用work來緩存的時候,就會出現(xiàn)問題脑融,導致版本不一致喻频,所以,個人感覺serviceWorker比較適合做一個輔助者肘迎,沒有也行甥温,有也行,可以通過設置一個button之類的來提醒用戶妓布,發(fā)現(xiàn)新版本姻蚓,是否需要刷新之類的操作。
下面這張圖很好的概括了生命周期
Fetch
激活完成之后匣沼,work內(nèi)可以攔截到fetch事件狰挡,通過這個,就可以執(zhí)行緩存操作肛著。fetch事件回調(diào)會返回一個fetchEvent對象圆兵,通過fetchEvent.respondWith()可以自定義返回的response。
其他事件
....
雜談
ServiceWorker的大多數(shù)功能都要在worker線程中使用枢贿,在頁面中殉农,通過注冊返回的ServiceWorkerRegistration可以獲取很多有用的信息,例如正在安裝的worker局荚,等待中的worker超凳,激活的worker愈污,worker作用域等。同時轮傍,可以監(jiān)聽狀態(tài)的變換暂雹。例如,
ServiceWorkerRegistration.addEventListener("updatefound",()=>{
const netWorker=ServiceWorkerRegistration.installing;
console.log(`state:${netWorker.state}`);
netWorker.addEventListener("statechange", () => {
// newWorker 狀態(tài)發(fā)生變化
console.log(`state變換:${netWorker.state}`)
if(netWorker.state=="installed"){
console.log("檢測到新版本!");
}
});
})
這里创夜,先獲取正在安裝中的worker杭跪,然后監(jiān)聽狀態(tài)變換,當切換成installed狀態(tài)時驰吓,假如之前已經(jīng)存在serviceWorker涧尿,就可以判定新安裝的worker是更新進去的,這個時候就有可能部分資源是通過老的worker緩存請求的檬贰,部分資源在worker受控之后是通過worker緩存請求的姑廉,這樣就存在不一致的問題,所以這時候就可以提醒用戶有新版本更新翁涤,從而刷新更新桥言。假如之前沒有worker,那么就說明這個worker是第一次注冊安裝葵礼,因為是第一次号阿,所以沒有所謂的緩存資源版本不一致的問題,沒必要提醒用戶更新章咧。
針對serviceWorker機制所導致的各種資源同步問題倦西,將其想像成漸進式的就輕松多了能真,將worker當作一個輔助者赁严,把緩存想象成可有可無的。進一步來看可以不使用skipWaiting粉铐,只有在用戶重啟的時候才可以更新疼约,這時候所有的資源就不存在不同步的問題了,所有的請求都會在重啟更新之后直接進入worker蝙泼。