Web Workers
web worker: 爲Web內容在後臺線程中運行腳本提供了一種簡單的方法,線程可以執行任務而不干擾用戶界面,即:運行在後臺的 JavaScript
瀏覽器一般有三類 web Worker:
- Worker:專用的 worker,只能被創建它的 JS 訪問,生命週期到創建它的頁面關閉時結束。
- SharedWorker:共享的 worker,可以被好幾個 JS 訪問,生命週期到關聯的頁面都關閉時結束。
ServiceWorker:一個特殊的 worker,生命週期與頁面無關,關聯頁面未關閉時,它也可以退出,沒有關聯頁面時,它也可以啓動,嗯總之就是一個神奇的worker
對於workers來說,它運行的上下文不同於當前的window對象所在的上下文,在專用worker的情況下,DedicatedWorkerGlobalScope 對象代表了worker的上下文;在共享worker的情況下,SharedWorkerGlobalScope對象代表了共享worker的上下文。
因此,在worker線程中運行的代碼是有一些特殊情況的,比如,不能直接操作DOM以及使用window對象的一些默認屬性和方法。但是在worker裏,還是有很多window對象之下的東西是可以使用的,具體就要查閱文檔了 https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Functions_and_classes_available_to_workers
worker線程和主線程各自都使用postMessage()發送消息和和onmessage事件來響應對方發送的消息,傳遞的信息包含在 Message 這個事件的data屬性內裏,數據傳遞的是副本。
當然一個 worker 可以生成另外的新的 worker,這些 worker 的宿主和它們父頁面的宿主相同。
Service Worker
前兩種worker主要是爲了解決js執行耗時操作時影響UI響應的問題,而之所以說service worker是一種特殊的worker,是因爲它想要把一個web APP變得更像native APP,可以支持離線訪問。在service worker之前,離線緩存使用AppCache來做,從Firefox44起,當使用 AppCache 來提供離線頁面支持時,會提示建議開發者使用 service workers 來實現離線頁面。
- 它是一種 JavaScript 工作線程,無法直接訪問 DOM。 service worker通過響應 postMessage 接口發送的消息來與其控制的頁面通信,頁面可在必要時對 DOM 執行操作。
- 它是一種可編程網絡代理,能夠控制並處理頁面所發送的網絡請求。
- 它在不用時會被中止,並在下次有需要時重啓,因此不能依賴於service worker的
onfetch事件
和onmessage事件
處理程序中的全局狀態。如果存在需要持續保存並在重啓後加以重用的信息,service worker可以訪問 IndexedDB API以及FireFox OS專用的Data Store API等數據存儲機制。 - service worker 廣泛利用了 promise
- service Workers 要求要在必須在 HTTPS 下才能運行,爲了便於本地開發,localhost 也被瀏覽器認爲是安全源。
生命週期
Service worker的生命週期完全獨立於網頁
- 如果要使用service worker,首先要在js中進行註冊,註冊的動作會讓瀏覽器在後臺啓動service worker的安裝步驟
- service worker獲取的第一個事件爲 install。該事件在工作線程執行時立即觸發,並且只能被每個service worker調用一次。 如果更改service worker的代碼,則瀏覽器將其視爲一個不同的service worker,並且它將獲得自己的 install 事件。在安裝的過程中,如果所有需要離線緩存的靜態資源都已經成功緩存,那麼service worker就安裝完成進入激活步驟,如果有文件下載失敗或緩存失敗,service worker就無法完成安裝過程。
- 安裝之後進入激活步驟,可以對舊的緩存進行管理
- 激活之後,service worker開始施展身手,對它作用域內的所有頁面進行控制,首次註冊service worker的頁面需要再次加載時纔會受控制。激活之後,service worker將處於以下兩種狀態之一:終止或處理onfetch和onmessage事件,從頁面發出網絡請求或消息後將會出現後一種狀態。
如果 service worker 腳本版本處於 ACTIVATED 狀態,功能事件處理完之後,service worker 線程會被終止,當再次有功能事件時,service worker 線程又會被啓動,啓動完成後 service worker 就可以立即進入 ACTIVATED 狀態。
瀏覽器內核會管理三種 service worker 腳本版本:
- installing_version:處於 INSTALLING 狀態的版本
- waiting_version:處於 INSTALLED 狀態的版本
- active_version:處於 ACTIVATED 狀態的版本
installing_version 一般是在 service worker 線程啓動後的版本,這是一箇中間版本,在正確安裝完成後會轉入 waiting_version。
waiting_version 一般在註冊信息已被存儲的版本狀態,或者在再次打開 service worker 頁面時,檢查到 service worker 腳本版本的狀態爲 INSTALLED,也會進入此版本狀態。waiting_version 的存在確保了當前 scope 下只有一個生效的 service worker。
active_version 一般在 activate 事件處理完成後,就會處於此版本狀態,同一 scope 下只有一個 active Service Worker。需要特別注意的是,當前頁面已有 active worker 控制,刷新頁面時,新版本 Waiting(Installed) 狀態的 service worker 並不能轉入 active 狀態。
Service worker 可以從 waiting_version 轉入 active_version 的條件:
- 當前 scope 下沒有 active service worker 在運行。
- 頁面 JS 調用 self.skipWaiting 跳過 waiting 狀態。
- 用戶關閉頁面,釋放了當前處於 active 狀態的 service worker。
- 瀏覽器週期性檢測,發現 active service worker 處於 idle 狀態,就會釋放當前處於 active 狀態的 service worker。
其中,INSTALLED和ACTIVATED是穩定狀態,可以對緩存之類的資源進行管理。
E.G.
1. 註冊
service worker需要在頁面中進行註冊才能啓動安裝步驟,註冊時需要告訴在service worker中執行的代碼的地址
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/sw.js').then(function(registration) {
// Registration was successful
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}).catch(function(err) {
// registration failed
console.log('ServiceWorker registration failed: ', err);
});
});
}
此代碼用於檢查 Service Worker API 是否可用,如果可用,則在頁面加載後註冊位於 /sw.js 的service worker。
每次頁面加載無誤時,即可調用 register();瀏覽器將會判斷service worker是否已註冊並做出相應的處理。
register() 方法的精妙之處在於service worker文件的位置。 本例中service worker文件位於根目錄。 這意味着service worker的作用域將是整個來源。 換句話說,service worker可以接收此網域上所有的 fetch 事件。 如果在 /example/sw.js 處註冊service worker文件,則service worker將只能看到URL以 /example/ 開頭(即 /example/page1/、/example/page2/)的頁面的 fetch 事件。
chrome://inspect/#service-workers 可以看到目前運行中的service worker
這是Google的一個例子
https://cdn.rawgit.com/jakearchibald/80368b84ac1ae8e229fc90b3fe826301/raw/ad55049bee9b11d47f1f7d19a73bf3306d156f43/
2. 安裝
啓動註冊後,需要爲安裝事件定義一個回調,決定哪些資源需要被緩存,在回調的內部,需要執行以下步驟:
1. 打開緩存
2. 緩存資源文件
3. 確定所有需要緩存的資源是否已經緩存完畢
在 install 事件中也可以執行其他任務,甚至不設置 install 事件偵聽器也可以。
var CACHE_NAME = 'my-site-cache-v1';
var urlsToCache = [
'/',
'/styles/main.css',
'/script/main.js'
];
self.addEventListener('install', function(event) {
// Perform install steps
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
3.開始工作
刷新當前頁面後,service worker將開始接收 fetch 事件
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
// Cache hit - return response
if (response) {
return response;
}
return fetch(event.request);
}
)
);
});
這裏定義了 fetch 事件,並且在 event.respondWith() 中傳入來自 caches.match() 的一個 promise。 此方法會檢車這個請求,並從service worker創建的緩存中查找緩存的結果。
如果發現有匹配的結果,則返回命中的值,否則調用 fetch 發出網絡請求,並將從服務器請求到的數據作爲結果返回。
4. 更新service worker
- 更新您的service worker JavaScript 文件。用戶訪問更新了之後的站點時,瀏覽器會嘗試在後臺重新下載service worker的腳本文件。如果service worker文件與其當前所用文件存在字節差異,則將其視爲“新service worker”。
- 新service worker將會啓動,且觸發 install 事件。
- 此時,舊service worker仍控制着當前頁面,新service worker將進入 waiting 狀態。
- 當網站上當前打開的頁面關閉時,舊service worker將會被終止,新service worker將會取得控制權。
- 新service worker取得控制權後,觸發其 activate 事件。(舊service worker退出時將觸發 新service worker的Activate,新service worker將能夠控制客戶端。 可以執行在仍使用舊工作線程時無法執行的操作,如遷移數據庫和清除緩存。)
出現在 activate 回調中的一個常見任務是緩存管理,但緩存管理通常不在這個新service worker的install回調裏面做,因爲它installed的時候舊的service worker還在工作中。
比如說我們有一個名爲 ‘my-site-cache-v1’ 的緩存,我們想要將該緩存拆分爲一個頁面緩存和一個博文緩存。這就意味着在安裝步驟中我們創建了兩個緩存:’pages-cache-v1’ 和 ‘blog-posts-cache-v1’,且在激活步驟中我們要刪除舊的 ‘my-site-cache-v1’。
具體做法爲:遍歷service worker中的所有緩存,並刪除未在緩存白名單中定義的任何緩存。
self.addEventListener('activate', function(event) {
var cacheWhitelist = ['pages-cache-v1', 'blog-posts-cache-v1'];
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.map(function(cacheName) {
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName);
}
})
);
})
);
});
等待階段表示每次只能運行一個版本的service worker,但可以通過調用 self.skipWaiting() 用新的service worker把舊的那個逐出去,並在進入等待階段時儘快激活自己(或立即激活,前提是已經處於等待階段)。
skipWaiting() 在等待期間調用還是在之前調用並沒有什麼不同。 一般情況下是在 install 事件中調用它:
self.addEventListener('install', event => {
self.skipWaiting();
event.waitUntil(
// caching etc
);
});