- 原文地址:How JavaScript works: Service Workers, their lifecycle and use cases
- 原文做者:Alexander Zlatkov
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:talisk
- 校對者:allen,趙立楊
這是專門探索 JavaScript 及其構建組件的系列的第八個。在識別和描述核心元素的過程當中,咱們也分享了一些咱們在構建 SessionStack 時的最佳實踐。SessionStack 是一個強大且性能卓越的 JavaScript 應用程序,能夠向你實時顯示用戶在 Web 應用程序中遇到技術問題或用戶體驗問題時的具體狀況。javascript
若是你沒看過以前的章節,你能夠在這裏看到:css
你可能已經知道了漸進式 Web 應用只會愈來愈受歡迎,由於它們旨在使 Web 應用的用戶體驗更加流暢,提供原生應用體驗而不是瀏覽器的外觀和感受。前端
構建漸進式 Web 應用程序的主要要求之一是使其在網絡和加載方面很是可靠 —— 它應該可用於不肯定或不可用的網絡條件。java
在這篇文章中,咱們將深刻探討 Service Worker:它們如何運做以及開發者應該關心什麼。最後,咱們還列出了開發者應該利用的 Service Worker 的一些獨特優點,並在 SessionStack 中分享咱們本身團隊的經驗。android
若是你想了解 Service Worker 的一切內容,你應該從閱讀咱們博客上,關於 Web Workers 的文章開始。ios
基本上,Service Worker 是 Web Worker 的一個類型,更具體地說,它像 Shared Worker:git
Service Worker API 使人興奮的主要緣由之一是它可讓你的網絡應用程序支持離線體驗,從而使開發人員可以徹底控制流程。github
Service Worker 的生命週期與你的網頁是徹底分開的,它由如下幾個階段組成:web
這是瀏覽器下載包含 Service Worker 的 .js
文件的時候。編程
要爲你的Web應用程序安裝 Service Worker,你必須先註冊它,你能夠在 JavaScript 代碼中進行註冊。當註冊 Service Worker 時,它會提示瀏覽器在後臺啓動 Service Worker 安裝步驟。
經過註冊 Service Worker,你能夠告訴瀏覽器你的 Service Worker 的 JavaScript 文件在哪裏。咱們來看下面的代碼:
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/sw.js').then(function(registration) {
// Registration was successful
console.log('ServiceWorker registration successful');
}, 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 只會看到以 /example/
開頭的頁面的 fetch
事件(例如 /example/page1/
、/example/page2/
)。
在安裝階段,最好加載和緩存一些靜態資源。資源成功緩存後,Service Worker 安裝完成。若是沒有成功(加載失敗)—— Service Worker 將重試。一旦安裝成功,靜態資源就已經在緩存中了。
若是註冊須要在加載事件以後發生,這就解答了你「註冊是否須要在加載事件以後發生」的疑惑。這不是必要的,但絕對是推薦的。
爲何這樣呢?讓咱們考慮用戶第一次訪問網絡應用程序的狀況。當前尚未 Service Worker,瀏覽器沒法事先知道最終是否會安裝 Service Worker。若是安裝了 Service Worker,則瀏覽器須要爲這個額外的線程承擔額外的 CPU 和內存開銷,不然瀏覽器會將計算資源用於渲染網頁上。
最重要的是,若是在頁面上安裝一個 Service Worker,就可能會有延遲加載和渲染的風險 —— 而不是儘快讓你的用戶可使用該頁面。
請注意,這種狀況僅僅是在第一次訪問頁面時很重要。後續頁面訪問不受 Service Worker 安裝的影響。一旦在第一次訪問頁面時激活 Service Worker,它能夠處理加載、緩存事件,以便隨後訪問 Web 應用程序。這一切都是有意義的,由於它須要準備好處理受限的的網絡鏈接。
安裝 Service Worker 以後,下一步是將它激活。這一步是管理以前緩存內容的好機會。
一旦激活,Service Worker 將開始控制全部屬於其範圍的頁面。一個有趣的事實是:首次註冊 Service Worker 的頁面將不會被控制,直到該頁面再次被加載。一旦 Service Worker 處於控制之下,它將處於如下狀態之一:
如下是生命週期的示意圖:
在頁面處理註冊過程以後,讓咱們看看 Service Worker 腳本中發生了什麼,它經過向 Service Worker 實例添加事件監聽來處理 install
事件。
這些是處理 install
事件時須要採起的步驟:
在 Service Worker 內部的一個簡單裝置可能會看起來像這樣:
var CACHE_NAME = 'my-web-app-cache';
var urlsToCache = [
'/',
'/styles/main.css',
'/scripts/app.js',
'/scripts/lib.js'
];
self.addEventListener('install', function(event) {
// event.waitUntil takes a promise to know how
// long the installation takes, and whether it
// succeeded or not.
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
複製代碼
若是全部文件都成功緩存,則將安裝 Service Worker。若是任何一個文件都沒法下載,則安裝步驟將失敗。因此要當心你放在那裏的文件。
處理 install
事件徹底是可選的,你能夠避免它,在這種狀況下,你不須要執行這裏的任何步驟。
這部分是貨真價實的內容。你將看到如何攔截請求並返回建立的緩存(以及建立新緩存)的位置。
在安裝 Service Worker 後,用戶進入了新的頁面,或者刷新當前頁面後,Service Worker 將收到 fetch 事件。 下面是一個演示如何返回緩存資源,或發送新請求後緩存結果的示例:
self.addEventListener('fetch', function(event) {
event.respondWith(
// This method looks at the request and
// finds any cached results from any of the
// caches that the Service Worker has created.
caches.match(event.request)
.then(function(response) {
// If a cache is hit, we can return thre response.
if (response) {
return response;
}
// Clone the request. A request is a stream and
// can only be consumed once. Since we are consuming this
// once by cache and once by the browser for fetch, we need
// to clone the request.
var fetchRequest = event.request.clone();
// A cache hasn't been hit so we need to perform a fetch, // which makes a network request and returns the data if // anything can be retrieved from the network. return fetch(fetchRequest).then( function(response) { // Check if we received a valid response if(!response || response.status !== 200 || response.type !== 'basic') { return response; } // Cloning the response since it's a stream as well.
// Because we want the browser to consume the response
// as well as the cache consuming the response, we need
// to clone it so we have two streams.
var responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(function(cache) {
// Add the request to the cache for future queries.
cache.put(event.request, responseToCache);
});
return response;
}
);
})
);
});
複製代碼
歸納地說這其中發生了什麼:
event.respondWith()
將決定咱們如何迴應 fetch
事件。咱們傳遞來自 caches.match()
的一個 promise,它檢查請求並查找是否有已經建立的緩存結果。fetch
。200
,同時檢查響應類型是 basic,代表響應來自咱們最初的請求。在這種狀況下,不會緩存對第三方資源的請求。請求和響應必須被複制,由於它們是流。流的 body 只能被使用一次。而且因爲咱們想消費它們,而瀏覽器也必須消費它們,所以咱們便須要克隆它們。
當有一個用戶訪問你的 web 應用,瀏覽器將嘗試從新下載包含了 Service Worker 的 .js
文件。這將在後臺執行。
若是與當前 Service Worker 的文件相比,新下載的 Service Worker 文件中存在哪怕一個字節的差別,則瀏覽器將會認爲有變動,且必須啓動新的 Service Worker。
新的 Service Worker 將啓動而且安裝事件將被移除。然而,在這一點上,舊的 Service Worker 仍在控制你的 web 應用的頁面,這意味着新的 Service Worker 將進入 waiting
狀態。
一旦你的 web 應用程序當前打開的頁面都被關掉,舊的 Service Worker 就會被瀏覽器幹掉,西南裝的 Service Worker 將徹底掌控應用。這就是它激活的事件將被幹掉的時候。
爲何須要這些?爲了不兩個版本的 Web 應用程序同時運行在不一樣的 tab 上 —— 這在網絡上實際上很是常見,而且可能會產生很是糟糕的錯誤(例如,在瀏覽器中本地存儲數據時,會有不一樣的 schema)。
activate
回調中最多見的步驟是緩存管理。咱們應該如今作這件事,由於若是你在安裝步驟中清除了全部舊緩存,舊的 Service Worker 將忽然中止提供緩存中的文件。
這裏提供了一個如何從緩存中刪除一些不在白名單中的文件的例子(在本例中,有 page-1
、page-2
兩個實體):
self.addEventListener('activate', function(event) {
var cacheWhitelist = ['page-1', 'page-2'];
event.waitUntil(
// Retrieving all the keys from the cache.
caches.keys().then(function(cacheNames) {
return Promise.all(
// Looping through all the cached files.
cacheNames.map(function(cacheName) {
// If the file in the cache is not in the whitelist
// it should be deleted.
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName);
}
})
);
})
);
});
複製代碼
在構建 Web 應用程序時,開發者能夠經過本地主機使用 Service Worker,可是一旦將其部署到生產環境中,則須要準備好 HTTPS(這是擁有 HTTPS 的最後一個緣由)。
使用 Service Worker,你能夠劫持鏈接並僞造響應。若不使用 HTTPS,你的 web 應用程序變得容易發生中間人攻擊。
爲了更安全,你須要在經過 HTTPS 提供的頁面上註冊 Service Worker,以便知道瀏覽器接收的 Service Worker 在經過網絡傳輸時未被修改。
瀏覽器對 Service Worker 的支持正在變得愈來愈好:
你能夠在這個網站上追蹤全部瀏覽器的適配進程 —— jakearchibald.github.io/isservicewo…。
Service Worker 提供的一些獨一無二的特性:
這些將在本系列將來的博客文章中詳細討論。
咱們一直致力於使 SessionStack 的用戶體驗儘量流暢,優化頁面加載和響應時間。
當你在 SessionStack(或實時觀看)中重播用戶會話時,SessionStack 前端將不斷從咱們的服務器提取數據,以便無縫地建立緩衝區,像你剛纔,同本文中同樣的經歷。爲了提供一些上下文 —— 一旦你將 SessionStack 的庫集成到 Web 應用程序中,它將不斷收集諸如 DOM 更改,用戶交互,網絡請求,未處理的異常和調試消息等數據。
當會話正在重播或實時流式傳輸時,SessionStack 會提供全部數據,讓開發者能夠在視覺和技術上查看用戶在本身的瀏覽器中體驗到的全部內容。這一切都須要快速實現,由於咱們不想讓用戶等待。
因爲數據是由咱們的前端提取的,所以這是一個很好的地方,能夠利用 Service Worker 來從新加載咱們的播放器,以及從新傳輸數據流等狀況。處理較慢的網絡鏈接也很是重要。
若是你想嘗試 SessionStack,這有個免費的計劃。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。