十年網(wǎng)站開發(fā)經(jīng)驗 + 多家企業(yè)客戶 + 靠譜的建站團隊
量身定制 + 運營維護+專業(yè)推廣+無憂售后,網(wǎng)站問題一站解決
「前置知識點」,只是做一個概念的介紹,不會做深度解釋。因為,這些概念在下面文章中會有出現(xiàn),為了讓行文更加的順暢,所以將本該在文內(nèi)的概念解釋放到前面來?!溉绻蠹覍@些概念熟悉,可以直接忽略」同時,由于閱讀我文章的群體有很多,所以有些知識點可能「我視之若珍寶,爾視只如草芥,棄之如敝履」。以下知識點,請「酌情使用」。

創(chuàng)新互聯(lián)是一家專注于成都網(wǎng)站建設、成都做網(wǎng)站與策劃設計,孝昌網(wǎng)站建設哪家好?創(chuàng)新互聯(lián)做網(wǎng)站,專注于網(wǎng)站建設十年,網(wǎng)設計領域的專業(yè)建站公司;建站業(yè)務涵蓋:孝昌等地區(qū)。孝昌做網(wǎng)站價格咨詢:18982081108
要查看正在運行的Service workers列表,我們可以在Chrome/Chromium中地址欄中輸入chrome://serviceworker-internals/。
圖片
chrome://xx 包含了很多內(nèi)置的功能,這塊也是有很大的說道的。后期,會單獨有一個專題來講。(已經(jīng)在籌劃準備中....)
Cache API為緩存的 Request / Response 對象對提供存儲機制。例如,作為ServiceWorker 生命周期的一部分
Cache API像 workers 一樣,是暴露在 window 作用域下的。盡管它被定義在 service worker 的標準中,但是它不必一定要配合 service worker 使用。
「一個域可以有多個命名 Cache 對象」。我們需要在腳本 (例如,在 ServiceWorker 中) 中處理緩存更新的方式。
緩存配額使用估算值,可以使用 StorageEstimate API 獲得。
瀏覽器盡其所能去管理磁盤空間,但它有可能刪除一個域下的緩存數(shù)據(jù)。
瀏覽器要么自動刪除特定域的全部緩存,要么全部保留。
一些圍繞service worker緩存的重要 API 方法包括:
Cache.put, Cache.add和Cache.addAll只能在GET請求下使用。
更多詳情可以參考MDN-Cache[1]
如果我們以前沒有使用過Cache接口,可能會認為它與 HTTP 緩存相同,或者至少與 HTTP 緩存相關。但實際情況并非如此。
可以將瀏覽器緩存看作是「分層的」。
Service workers是JavaScript層面的 API,「充當 Web 瀏覽器和 Web 服務器之間的代理」。它們的目標是通過提供離線訪問以及提升頁面性能來提高可靠性。
Service workers是對現(xiàn)有網(wǎng)站的增強。這意味著如果使用Service workers的網(wǎng)站的用戶使用不支持Service workers的瀏覽器訪問網(wǎng)站,基本功能不會受到破壞。它是向下兼容的。
Service workers通過類似于桌面應用程序的生命周期逐漸增強網(wǎng)站。想象一下當從應用商城安裝APP時會發(fā)生流程:
Service worker也采用類似的生命周期,但采用「漸進增強」的方法。
Service worker技術中不可或缺的一部分是Cache API,這是一種「完全獨立于 HTTP 緩存的緩存機制」。Cache API可以在Service worker作用域內(nèi)和「主線程」作用域內(nèi)訪問。該特性為用戶操作與 Cache 實例的交互提供了許多可能性。
這意味著可以根據(jù)網(wǎng)站的特有的邏輯來緩存網(wǎng)絡請求的響應。例如:
這些都是緩存策略的應用方向。緩存策略使離線體驗成為可能,并「通過繞過 HTTP 緩存觸發(fā)的高延遲重新驗證檢查提供更好的性能」。
在「網(wǎng)絡上傳輸數(shù)據(jù)本質(zhì)上是異步的」。請求資產(chǎn)、服務器響應請求以及下載響應都需要時間。所涉及的時間是多樣且不確定的。Service workers通過「事件驅(qū)動」的 API 來適應這種異步性,「使用回調(diào)處理事件」,例如:
都可以使用addEventListener API 注冊事件。所有這些事件都可以與Cache API進行交互。特別是在網(wǎng)絡請求是離散的,運行回調(diào)的能力對于「提供所期望的可靠性和速度」至關重要。
在JavaScript中進行異步工作涉及使用Promises。因為Promises也支持async和await,這些JavaScript特性也可用于簡化Service worker代碼,從而提供更好的開發(fā)者體驗。
Service worker與Cache實例之間的交互涉及兩個不同的緩存概念:
預緩存是需要提前緩存資源的過程,通常在Service worker「安裝期間」進行。通過預緩存,「關鍵的靜態(tài)資產(chǎn)和離線訪問所需的材料可以被下載并存儲在 Cache 實例中」。這種類型的緩存還可以提高需要預緩存資源的后續(xù)頁面的頁面速度。
運行時緩存是指在運行時從網(wǎng)絡請求資源時應用緩存策略。這種類型的緩存非常有用,因為它保證了用戶已經(jīng)訪問過的頁面和資源的離線訪問。
當在Service worker中使用這些方法時,可以為用戶體驗提供巨大的好處,并為普通的網(wǎng)頁提供類似應用程序的行為。
Service workers與Web workers類似,它們的「所有工作都在自己的線程上進行」。這意味著Service workers的任務不會與主線程上的其他任務競爭。
我們就以Web Worker為例子,做一個簡單的演示 在JavaScript中創(chuàng)建Web Worker并不是一項復雜的任務。
創(chuàng)建一個新的JavaScript文件,其中包含我們希望在工作線程中運行的代碼。此文件不應包含對DOM的任何引用,因為它將無法訪問DOM。
在我們的主JavaScript文件中,使用Worker構(gòu)造函數(shù)創(chuàng)建一個新的Worker對象。此構(gòu)造函數(shù)接受一個參數(shù),即我們在第1步中創(chuàng)建的JavaScript文件的URL。
const worker = new Worker('worker.js');為Worker對象添加事件偵聽器,以處理主線程和工作線程之間發(fā)送的消息。onmessage事件處理程序用于處理從工作線程發(fā)送的消息,而postMessage方法用于向工作線程發(fā)送消息。
worker.onmessage = function(event) {
console.log('Worker said: ' + event.data);
};
worker.postMessage('Hello, worker!');在我們的工作線程JavaScript文件中,添加一個事件偵聽器,以處理從主線程發(fā)送的消息,使用self對象的onmessage屬性。我們可以使用event.data屬性訪問消息中發(fā)送的數(shù)據(jù)。
self.onmessage = function(event) {
console.log('Main thread said: ' + event.data);
self.postMessage('Hello, main thread!');
};現(xiàn)在讓我們運行Web應用程序并測試Worker。我們應該在控制臺中看到打印的消息,指示主線程和工作線程之間已發(fā)送和接收消息。
圖片
在深入了解service worker的生命周期之前,我們先來了解一下與生命周期運作相關的「術語」(黑話)
了解service worker運作方式的關鍵在于理解「控制」(control)。
一個service worker的作用域由其「在 Web 服務器上的位置確定」。如果一個service worker在位于/A/index.html的頁面上運行,并且位于/A/sw.js上,那么該service worker的作用域就是/A/。
作用域限制了service worker控制的頁面。在上面的例子中,這意味著從/subdir/sw.js加載的service worker只能「控制位于/subdir/或其子頁面中」。
控制頁面的service worker仍然可以「攔截任何網(wǎng)絡請求」,包括跨域資源的請求。作用域限制了由service worker控制的頁面。
上述是默認情況下作用域工作的方式,但可以通過設置Service-Worker-Allowed響應頭,以及通過向register方法傳遞作用域選項來進行覆蓋。
除非有很好的理由將service worker的作用域限制為origin的子集,否則應「從 Web 服務器的根目錄加載service worker,以便其作用域盡可能廣泛」,不必擔心Service-Worker-Allowed頭部。
當說一個service worker正在控制一個頁面時,實際上「是在控制一個客戶端」??蛻舳耸侵窾RL位于該service worker作用域內(nèi)的「任何打開的頁面」。具體來說,這些是WindowClient的實例。
圖片
為了使service worker能夠控制頁面,首先必須將其部署。
讓我們看看一個沒有service worker的網(wǎng)站到部署全新service worker時,中間發(fā)生了啥?
注冊是service worker生命周期的「初始步驟」:
此代碼在「主線程」上運行,并執(zhí)行以下操作:
還有一些關鍵要點:
一旦注冊完成,「安裝」就開始了。
service worker在注冊后觸發(fā)其install事件。install「只會在每個service worker中調(diào)用一次,直到它被更新才會再次觸發(fā)」。可以使用addEventListener在worker的作用域內(nèi)注冊install事件的回調(diào):
// /sw.js
self.addEventListener("install", (event) => {
const cacheKey = "前端柒八九_v1";
event.waitUntil(
caches.open(cacheKey).then((cache) => {
// 將數(shù)組中的所有資產(chǎn)添加到'前端柒八九_v1'的`Cache`實例中以供以后使用。
return cache.addAll([
"/css/global.bc7b80b7.css",
"/css/home.fe5d0b23.css",
"/js/home.d3cc4ba4.js",
"/js/A.43ca4933.js",
]);
})
);
});這會創(chuàng)建一個新的Cache實例并對資產(chǎn)進行「預緩存」。其中有一個event.waitUntil。event.waitUntil接受一個Promise,并等待該Promise被解決。
在這個示例中,這個Promise執(zhí)行兩個異步操作:
如果傳遞給event.waitUntil的Promise被「拒絕,安裝將失敗」。如果發(fā)生這種情況,service worker將被「丟棄」。
如果Promise被解決,安裝成功,service worker的狀態(tài)將更改為installed,然后進入「激活」階段。
如果注冊和安裝成功,service worker將被「激活」,其狀態(tài)將變?yōu)閍ctivating。在service worker的activate事件中可以進行激活期間的工作。在此事件中的一個典型任務是「清理舊緩存」,但對于「全新 service worker」,目前還不相關。
對于新的service worker,「安裝成功后,激活會立即觸發(fā)」。一旦激活完成,service worker的狀態(tài)將變?yōu)閍ctivated。
默認情況下,新的service worker直到「下一次導航或頁面刷新之前才會開始控制頁面」。
一旦部署了第一個service worker,它很可能需要在以后進行更新。例如,如果請求處理或預緩存邏輯發(fā)生了變化,就可能需要進行更新。
瀏覽器會在以下情況下檢查service worker的更新:
了解瀏覽器何時更新service worker很重要,但“如何”也很重要。假設service worker的URL或作用域未更改,「只有在其內(nèi)容發(fā)生變化時,當前安裝的service worker才會更新到新版本」。
瀏覽器以幾種方式檢測變化:
為確保瀏覽器能夠可靠地檢測service worker內(nèi)容的變化,「不要使用 HTTP 緩存保留它,也不要更改其文件名」。當導航到service worker作用域內(nèi)的新頁面時,瀏覽器會自動執(zhí)行更新檢查。
關于更新,注冊邏輯通常不應更改。然而,一個例外情況可能是「網(wǎng)站上的會話持續(xù)時間很長」。這可能在「單頁應用程序」中發(fā)生,因為導航請求通常很少,應用程序通常在應用程序生命周期的開始遇到一個導航請求。在這種情況下,可以在「主線程上手動觸發(fā)更新」:
navigator.serviceWorker.ready.then((registration) => {
registration.update();
});對于傳統(tǒng)的網(wǎng)站,或者在用戶會話不持續(xù)很長時間的任何情況下,手動更新可能不是必要的。
當使用打包工具生成「靜態(tài)資源」時,這些資源的「名稱中會包含哈希值」,例如framework.3defa9d2.js。假設其中一些資源被預緩存以供以后離線訪問,這將需要對service worker進行更新以預緩存新的資源:
self.addEventListener("install", (event) => {
const cacheKey = "前端柒八九_v2";
event.waitUntil(
caches.open(cacheKey).then((cache) => {
// 將數(shù)組中的所有資產(chǎn)添加到'前端柒八九_v2'的`Cache`實例中以供以后使用。
return cache.addAll([
"/css/global.ced4aef2.css",
"/css/home.cbe409ad.css",
"/js/home.109defa4.js",
"/js/A.38caf32d.js",
]);
})
);
});與之前的install事件示例有兩個方面不同:
更新后的service worker會與先前的service worker并存。這意味著舊的service worker仍然控制著任何打開的頁面。剛才安裝的新的service worker進入等待狀態(tài),直到被激活。
默認情況下,新的service worker將在「沒有任何客戶端由舊的service worker控制時激活」。這發(fā)生在相關網(wǎng)站的所有打開標簽都關閉時。
當安裝了新的service worker并結(jié)束了等待階段時,它會被激活,并丟棄舊的service worker。在更新后的service worker的activate事件中執(zhí)行的常見任務是「清理舊緩存」。通過使用caches.keys獲取所有打開的 Cache 實例的key,并使用caches.delete刪除不在允許列表中的所有舊緩存:
self.addEventListener("activate", (event) => {
// 指定允許的緩存密鑰
const cacheAllowList = ["前端柒八九_v2"];
// 獲取當前活動的所有`Cache`實例。
event.waitUntil(
caches.keys().then((keys) => {
// 刪除不在允許列表中的所有緩存:
return Promise.all(
keys.map((key) => {
if (!cacheAllowList.includes(key)) {
return caches.delete(key);
}
})
);
})
);
});舊的緩存不會自動清理。我們需要自己來做,否則可能會超過存儲配額。
由于第一個service worker中的前端柒八九_v1已經(jīng)過時,緩存允許列表已更新為指定前端柒八九_v2,這將刪除具有不同名稱的緩存。
「激活事件在舊緩存被刪除后完成」。此時,新的service worker將控制頁面,最終替代舊的service worker!
要有效使用service worker,有必要采用一個或多個緩存策略,這需要對Cache API有一定的了解。
緩存策略是service worker的fetch事件與Cache API之間的交互。如何編寫緩存策略取決于不同情況。
緩存策略的另一個重要的用途就是與service worker的fetch事件配合使用。我們已經(jīng)聽說過一些關于「攔截網(wǎng)絡請求」的內(nèi)容,而service worker內(nèi)部的fetch事件就是處理這種情況的:
// 建立緩存名稱
const cacheName = "前端柒八九_v1";
self.addEventListener("install", (event) => {
event.waitUntil(caches.open(cacheName));
});
self.addEventListener("fetch", async (event) => {
// 這是一個圖片請求
if (event.request.destination === "image") {
// 打開緩存
event.respondWith(
caches.open(cacheName).then((cache) => {
// 從緩存中響應圖片,如果緩存中沒有,就從網(wǎng)絡獲取圖片
return cache.match(event.request).then((cachedResponse) => {
return (
cachedResponse ||
fetch(event.request.url).then((fetchedResponse) => {
// 將網(wǎng)絡響應添加到緩存以供將來訪問。
// 注意:我們需要復制響應以保存在緩存中,同時使用原始響應作為請求的響應。
cache.put(event.request, fetchedResponse.clone());
// 返回網(wǎng)絡響應
return fetchedResponse;
})
);
});
})
);
} else {
return;
}
});上面的代碼執(zhí)行以下操作:
fetch事件的事件對象包含一個request屬性,其中包含一些有用的信息,可幫助我們識別每個請求的類型:
「異步操作是關鍵」。我們還記得install事件提供了一個event.waitUntil方法,它接受一個promise,并在激活之前等待其解析。fetch事件提供了類似的event.respondWith方法,我們可以使用它來返回異步fetch請求的結(jié)果或Cache接口的match方法返回的響應。
展示了從頁面到service worker到緩存的流程。
「僅緩存」運作方式:當service worker控制頁面時,「匹配的請求只會進入緩存」。這意味著為了使該模式有效,「任何緩存的資源都需要在安裝時進行預緩存」,而「這些資源在service worker更新之前將不會在緩存中進行更新」。
// 建立緩存名稱
const cacheName = "前端柒八九_v1";
// 要預緩存的資產(chǎn)
const preCachedAssets = ["/A.jpg", "/B.jpg", "/C.jpg", "/D.jpg"];
self.addEventListener("install", (event) => {
// 在安裝時預緩存資產(chǎn)
event.waitUntil(
caches.open(cacheName).then((cache) => {
return cache.addAll(preCachedAssets);
})
);
});
self.addEventListener("fetch", (event) => {
const url = new URL(event.request.url);
const isPrecachedRequest = preCachedAssets.includes(url.pathname);
if (isPrecachedRequest) {
// 從緩存中獲取預緩存的資產(chǎn)
event.respondWith(
caches.open(cacheName).then((cache) => {
return cache.match(event.request.url);
})
);
} else {
// 轉(zhuǎn)到網(wǎng)絡
return;
}
});在上面的示例中,數(shù)組中的資產(chǎn)在安裝時被預緩存。當service worker處理fetch請求時,我們「檢查fetch事件處理的請求 URL 是否在預緩存資產(chǎn)的數(shù)組中」。
圖片
「僅網(wǎng)絡」的策略與「僅緩存」相反,它將請求通過service worker傳遞到網(wǎng)絡,而「不與 service worker 緩存進行任何交互」。這是一種「確保內(nèi)容新鮮度」的好策略,但其權(quán)衡是「當用戶離線時將無法正常工作」。
要確保請求直接通過到網(wǎng)絡,只需「不對匹配的請求調(diào)用 event.respondWith」。如果我們想更明確,可以在要傳遞到網(wǎng)絡的請求的fetch事件回調(diào)中加入一個空的return;。這就是「僅緩存」策略演示中對于未經(jīng)預緩存的請求所發(fā)生的情況。
圖片
對于「匹配的請求」,流程如下:
// 建立緩存名稱
const cacheName = "前端柒八九_v1";
self.addEventListener("fetch", (event) => {
// 檢查這是否是一個圖像請求
if (event.request.destination === "image") {
event.respondWith(
caches.open(cacheName).then((cache) => {
// 首先從緩存中獲取
return cache.match(event.request.url).then((cachedResponse) => {
// 如果我們有緩存的響應,則返回緩存的響應
if (cachedResponse) {
return cachedResponse;
}
// 否則,訪問網(wǎng)絡
return fetch(event.request).then((fetchedResponse) => {
// 將網(wǎng)絡響應添加到緩存以供以后訪問
cache.put(event.request, fetchedResponse.clone());
// 返回網(wǎng)絡響應
return fetchedResponse;
});
});
})
);
} else {
return;
}
});盡管這個示例只涵蓋了圖像,但這是一個很好的范例,「適用于所有靜態(tài)資產(chǎn)」(如CSS、JavaScript、圖像和字體),「尤其是哈希版本的資產(chǎn)」。它「通過跳過 HTTP 緩存可能啟動的任何與服務器的內(nèi)容新鮮度檢查,為不可變資產(chǎn)提供了速度提升」。更重要的是,「任何緩存的資產(chǎn)都將在離線時可用」。
它的含義就是:
這種策略對于HTML或 API 請求非常有用,當在線時,我們希望獲取資源的最新版本,但希望在離線時能夠訪問最新可用的版本。
// 建立緩存名稱
const cacheName = "前端柒八九_v1";
self.addEventListener("fetch", (event) => {
// 檢查這是否是導航請求
if (event.request.mode === "navigate") {
// 打開緩存
event.respondWith(
caches.open(cacheName).then((cache) => {
// 首先通過網(wǎng)絡請求
return fetch(event.request.url)
.then((fetchedResponse) => {
cache.put(event.request, fetchedResponse.clone());
return fetchedResponse;
})
.catch(() => {
// 如果網(wǎng)絡不可用,從緩存中獲取
return cache.match(event.request.url);
});
})
);
} else {
return;
}
});在需要重視離線功能,但又需要平衡該功能與獲取一些標記或 API 數(shù)據(jù)的最新版本的情況下,「網(wǎng)絡優(yōu)先,備用緩存」是一種實現(xiàn)這一目標的可靠策略。
圖片
「陳舊時重新驗證」策略是其中最復雜的。該策略的過程「優(yōu)先考慮了資源的訪問速度」,同時在后臺保持其更新。該策略的工作流程如下:
這是一個適用于「需要保持更新但不是絕對必要的資源」的策略,比如網(wǎng)站的頭像。它們會在用戶愿意更新時進行更新,但不一定需要在每次請求時獲取最新版本。
// 建立緩存名稱
const cacheName = "前端柒八九_v1";
self.addEventListener("fetch", (event) => {
if (event.request.destination === "image") {
event.respondWith(
caches.open(cacheName).then((cache) => {
return cache.match(event.request).then((cachedResponse) => {
const fetchedResponse = fetch(event.request).then(
(networkResponse) => {
cache.put(event.request, networkResponse.clone());
return networkResponse;
}
);
return cachedResponse || fetchedResponse;
});
})
);
} else {
return;
}
});如果將預緩存「應用于太多的資產(chǎn)」,或者如果Service Worker在頁面「完成加載關鍵資產(chǎn)之前」就注冊了,那么可能會遇到問題。
當Service Worker在「安裝期間預緩存資產(chǎn)時,將同時發(fā)起一個或多個網(wǎng)絡請求」。如果時機不合適,這可能會對用戶體驗產(chǎn)生問題。即使時機剛剛好,如果未對預緩存資產(chǎn)的「數(shù)量進行限制」,仍可能會浪費數(shù)據(jù)。
如果Service Worker預緩存任何內(nèi)容,那么它的注冊時機很重要。Service Worker通常使用內(nèi)聯(lián)的