偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

WorkBox 之底層邏輯 Service Worker

開發(fā) 前端
Chrome DevTools? 的 Application? 面板有一個(gè)存儲(chǔ)子面板,提供了有關(guān)頁面使用的當(dāng)前存儲(chǔ)配額的信息。它還允許指定以兆字節(jié)為單位的自定義配額。一旦生效,Chrome 將執(zhí)行自定義存儲(chǔ)配額以進(jìn)行測試。

1. 前置知識(shí)點(diǎn)

「前置知識(shí)點(diǎn)」,只是做一個(gè)概念的介紹,不會(huì)做深度解釋。因?yàn)?,這些概念在下面文章中會(huì)有出現(xiàn),為了讓行文更加的順暢,所以將本該在文內(nèi)的概念解釋放到前面來?!溉绻蠹覍?duì)這些概念熟悉,可以直接忽略」同時(shí),由于閱讀我文章的群體有很多,所以有些知識(shí)點(diǎn)可能「我視之若珍寶,爾視只如草芥,棄之如敝履」。以下知識(shí)點(diǎn),請(qǐng)「酌情使用」。

如何查看Service Worker

要查看正在運(yùn)行的Service workers列表,我們可以在Chrome/Chromium中地址欄中輸入chrome://serviceworker-internals/。

圖片圖片

chrome://xx 包含了很多內(nèi)置的功能,這塊也是有很大的說道的。后期,會(huì)單獨(dú)有一個(gè)專題來講。(已經(jīng)在籌劃準(zhǔn)備中....)

Cache API

Cache API為緩存的 Request / Response 對(duì)象對(duì)提供存儲(chǔ)機(jī)制。例如,作為ServiceWorker 生命周期的一部分

Cache API像 workers 一樣,是暴露在 window 作用域下的。盡管它被定義在 service worker 的標(biāo)準(zhǔn)中,但是它不必一定要配合 service worker 使用。

「一個(gè)域可以有多個(gè)命名 Cache 對(duì)象」。我們需要在腳本 (例如,在 ServiceWorker 中) 中處理緩存更新的方式。

  • 除非明確地更新緩存,否則緩存將不會(huì)被更新;
  • 除非刪除,否則緩存數(shù)據(jù)不會(huì)過期
  • 使用 CacheStorage.open(cacheName) 打開一個(gè) Cache 對(duì)象,再使用 Cache 對(duì)象的方法去處理緩存。
  • 需要定期地清理緩存條目,因?yàn)槊總€(gè)瀏覽器都硬性限制了一個(gè)域下緩存數(shù)據(jù)的大小。

緩存配額使用估算值,可以使用 StorageEstimate API 獲得。

瀏覽器盡其所能去管理磁盤空間,但它有可能刪除一個(gè)域下的緩存數(shù)據(jù)。

瀏覽器要么自動(dòng)刪除特定域的全部緩存,要么全部保留。

一些圍繞service worker緩存的重要 API 方法包括:

  • CacheStorage.open用于創(chuàng)建新的 Cache 實(shí)例。
  • Cache.add和Cache.put用于將「網(wǎng)絡(luò)響應(yīng)」存儲(chǔ)在service worker緩存中。
  • Cache.match用于查找 Cache 實(shí)例中的緩存響應(yīng)。
  • Cache.delete用于從 Cache 實(shí)例中刪除緩存響應(yīng)。
  • .....

Cache.put, Cache.add和Cache.addAll只能在GET請(qǐng)求下使用。

更多詳情可以參考MDN-Cache[1]

Cache API 與 HTTP 緩存的區(qū)別

如果我們以前沒有使用過Cache接口,可能會(huì)認(rèn)為它與 HTTP 緩存相同,或者至少與 HTTP 緩存相關(guān)。但實(shí)際情況并非如此。

  • Cache接口是一個(gè)「完全獨(dú)立于」HTTP 緩存的緩存機(jī)制
  • 用于影響HTTP緩存的任何Cache-Control配置對(duì)存儲(chǔ)在Cache接口中的資源沒有影響。

可以將瀏覽器緩存看作是「分層的」。

  • HTTP緩存是一個(gè)由「鍵-值對(duì)驅(qū)動(dòng)」的「低級(jí)緩存」,其中的指令在HTTP Header中表示。
  • Cache接口是由「JavaScript API 驅(qū)動(dòng)」的「高級(jí)緩存」。這比使用相對(duì)簡單的HTTP鍵-值對(duì)具有更大的靈活性。

2. Service Workers 能為我們帶來什么

Service workers是JavaScript層面的 API,「充當(dāng) Web 瀏覽器和 Web 服務(wù)器之間的代理」。它們的目標(biāo)是通過提供離線訪問以及提升頁面性能來提高可靠性。

漸進(jìn)增強(qiáng),類似應(yīng)用程序生命周期

Service workers是對(duì)現(xiàn)有網(wǎng)站的增強(qiáng)。這意味著如果使用Service workers的網(wǎng)站的用戶使用不支持Service workers的瀏覽器訪問網(wǎng)站,基本功能不會(huì)受到破壞。它是向下兼容的。

Service workers通過類似于桌面應(yīng)用程序的生命周期逐漸增強(qiáng)網(wǎng)站。想象一下當(dāng)從應(yīng)用商城安裝APP時(shí)會(huì)發(fā)生流程:

  • 發(fā)出下載APP的請(qǐng)求。
  • APP下載并安裝。
  • APP準(zhǔn)備好使用并可以啟動(dòng)。
  • APP進(jìn)行新版本的更新。

Service worker也采用類似的生命周期,但采用「漸進(jìn)增強(qiáng)」的方法。

  • 在首次訪問安裝了新Service worker的網(wǎng)頁時(shí),初始訪問提供網(wǎng)站的基本功能,同時(shí)Service worker開始「下載」。
  • 「安裝」和「激活」Service worker后,它將控制頁面以提供更高的可靠性和速度。

采用 JavaScript 驅(qū)動(dòng)的 Cache API

Service worker技術(shù)中不可或缺的一部分是Cache API,這是一種「完全獨(dú)立于 HTTP 緩存的緩存機(jī)制」。Cache API可以在Service worker作用域內(nèi)和「主線程」作用域內(nèi)訪問。該特性為用戶操作與 Cache 實(shí)例的交互提供了許多可能性。

  • HTTP緩存是通過HTTP Header中指定的「緩存指令」來影響的
  • Cache API可以「通過 JavaScript 進(jìn)行編程」

這意味著可以根據(jù)網(wǎng)站的特有的邏輯來緩存網(wǎng)絡(luò)請(qǐng)求的響應(yīng)。例如:

  • 在「首次請(qǐng)求靜態(tài)資源時(shí)」將其存儲(chǔ)在緩存中,然后在「后續(xù)請(qǐng)求中從緩存中獲取」。
  • 將頁面結(jié)構(gòu)存儲(chǔ)在緩存中,但在「離線情況下」從緩存中獲取。
  • 對(duì)于一些「非緊急的資源」,先從緩存中獲取,然后在后臺(tái)中通過網(wǎng)絡(luò)再更新它。下次再獲取該資源時(shí)候,就認(rèn)為是最新的
  • 網(wǎng)絡(luò)采用「流式傳輸」處理部分內(nèi)容,并與緩存中的應(yīng)用程序攔截層組合以改善感知性能。

這些都是緩存策略的應(yīng)用方向。緩存策略使離線體驗(yàn)成為可能,并「通過繞過 HTTP 緩存觸發(fā)的高延遲重新驗(yàn)證檢查提供更好的性能」。

異步和事件驅(qū)動(dòng)的 API

在「網(wǎng)絡(luò)上傳輸數(shù)據(jù)本質(zhì)上是異步的」。請(qǐng)求資產(chǎn)、服務(wù)器響應(yīng)請(qǐng)求以及下載響應(yīng)都需要時(shí)間。所涉及的時(shí)間是多樣且不確定的。Service workers通過「事件驅(qū)動(dòng)」的 API 來適應(yīng)這種異步性,「使用回調(diào)處理事件」,例如:

  • 當(dāng)Service worker正在「安裝」時(shí)。
  • 當(dāng)Service worker正在「激活」時(shí)。
  • 當(dāng)Service worker檢測到網(wǎng)絡(luò)請(qǐng)求時(shí)。

都可以使用addEventListener API 注冊事件。所有這些事件都可以與Cache API進(jìn)行交互。特別是在網(wǎng)絡(luò)請(qǐng)求是離散的,運(yùn)行回調(diào)的能力對(duì)于「提供所期望的可靠性和速度」至關(guān)重要。

在JavaScript中進(jìn)行異步工作涉及使用Promises。因?yàn)镻romises也支持async和await,這些JavaScript特性也可用于簡化Service worker代碼,從而提供更好的開發(fā)者體驗(yàn)。

預(yù)緩存和運(yùn)行時(shí)緩存

Service worker與Cache實(shí)例之間的交互涉及兩個(gè)不同的緩存概念:

  • 「預(yù)緩存」(Precaching caching)
  • 「運(yùn)行時(shí)緩存」(Runtime caching)

預(yù)緩存是需要提前緩存資源的過程,通常在Service worker「安裝期間」進(jìn)行。通過預(yù)緩存,「關(guān)鍵的靜態(tài)資產(chǎn)和離線訪問所需的材料可以被下載并存儲(chǔ)在 Cache 實(shí)例中」。這種類型的緩存還可以提高需要預(yù)緩存資源的后續(xù)頁面的頁面速度。

運(yùn)行時(shí)緩存是指在運(yùn)行時(shí)從網(wǎng)絡(luò)請(qǐng)求資源時(shí)應(yīng)用緩存策略。這種類型的緩存非常有用,因?yàn)樗WC了用戶已經(jīng)訪問過的頁面和資源的離線訪問。

當(dāng)在Service worker中使用這些方法時(shí),可以為用戶體驗(yàn)提供巨大的好處,并為普通的網(wǎng)頁提供類似應(yīng)用程序的行為。

與主線程隔離

Service workers與Web workers類似,它們的「所有工作都在自己的線程上進(jìn)行」。這意味著Service workers的任務(wù)不會(huì)與主線程上的其他任務(wù)競爭。

我們就以Web Worker為例子,做一個(gè)簡單的演示 在JavaScript中創(chuàng)建Web Worker并不是一項(xiàng)復(fù)雜的任務(wù)。

創(chuàng)建一個(gè)新的JavaScript文件,其中包含我們希望在工作線程中運(yùn)行的代碼。此文件不應(yīng)包含對(duì)DOM的任何引用,因?yàn)樗鼘o法訪問DOM。

在我們的主JavaScript文件中,使用Worker構(gòu)造函數(shù)創(chuàng)建一個(gè)新的Worker對(duì)象。此構(gòu)造函數(shù)接受一個(gè)參數(shù),即我們在第1步中創(chuàng)建的JavaScript文件的URL。

const worker = new Worker('worker.js');

為Worker對(duì)象添加事件偵聽器,以處理主線程和工作線程之間發(fā)送的消息。onmessage事件處理程序用于處理從工作線程發(fā)送的消息,而postMessage方法用于向工作線程發(fā)送消息。

worker.onmessage = function(event) {
  console.log('Worker said: ' + event.data);
};
worker.postMessage('Hello, worker!');

在我們的工作線程JavaScript文件中,添加一個(gè)事件偵聽器,以處理從主線程發(fā)送的消息,使用self對(duì)象的onmessage屬性。我們可以使用event.data屬性訪問消息中發(fā)送的數(shù)據(jù)。

self.onmessage = function(event) {
  console.log('Main thread said: ' + event.data);
  self.postMessage('Hello, main thread!');
};

現(xiàn)在讓我們運(yùn)行Web應(yīng)用程序并測試Worker。我們應(yīng)該在控制臺(tái)中看到打印的消息,指示主線程和工作線程之間已發(fā)送和接收消息。

圖片圖片

3. Service worker 的生命周期

定義術(shù)語

在深入了解service worker的生命周期之前,我們先來了解一下與生命周期運(yùn)作相關(guān)的「術(shù)語」(黑話)

控制和作用域

了解service worker運(yùn)作方式的關(guān)鍵在于理解「控制」(control)。

  • 由service worker控制的頁面允許service worker代表該頁面進(jìn)行攔截網(wǎng)絡(luò)請(qǐng)求。
  • 在給定的「作用域」(scope)內(nèi),service worker能夠?yàn)轫撁鎴?zhí)行處理資源的相關(guān)工作。

作用域

一個(gè)service worker的作用域由其「在 Web 服務(wù)器上的位置確定」。如果一個(gè)service worker在位于/A/index.html的頁面上運(yùn)行,并且位于/A/sw.js上,那么該service worker的作用域就是/A/。

  • 打開https://service-worker-scope-viewer.glitch.me/subdir/index.html。將顯示一條消息,說明沒有service worker正在「控制」該頁面。但是,該頁面從https://service-worker-scope-viewer.glitch.me/subdir/sw.js注冊了一個(gè)service worker。
  • 「重新加載頁面」。因?yàn)閟ervice worker「已經(jīng)注冊并處于活動(dòng)狀態(tài)」,它正在「控制」頁面。將顯示一個(gè)包含service worker作用域、當(dāng)前狀態(tài)和其 URL 的表單。
  • 現(xiàn)在打開https://service-worker-scope-viewer.glitch.me/index.html。盡管在此origin上注冊了一個(gè)service worker,但仍然會(huì)顯示一條消息,說明沒有當(dāng)前的service worker。這是因?yàn)榇隧撁娌辉谝炎詓ervice worker的作用域內(nèi)。

作用域限制了service worker控制的頁面。在上面的例子中,這意味著從/subdir/sw.js加載的service worker只能「控制位于/subdir/或其子頁面中」。

控制頁面的service worker仍然可以「攔截任何網(wǎng)絡(luò)請(qǐng)求」,包括跨域資源的請(qǐng)求。作用域限制了由service worker控制的頁面。

上述是默認(rèn)情況下作用域工作的方式,但可以通過設(shè)置Service-Worker-Allowed響應(yīng)頭,以及通過向register方法傳遞作用域選項(xiàng)來進(jìn)行覆蓋。

除非有很好的理由將service worker的作用域限制為origin的子集,否則應(yīng)「從 Web 服務(wù)器的根目錄加載service worker,以便其作用域盡可能廣泛」,不必?fù)?dān)心Service-Worker-Allowed頭部。

客戶端

當(dāng)說一個(gè)service worker正在控制一個(gè)頁面時(shí),實(shí)際上「是在控制一個(gè)客戶端」??蛻舳耸侵窾RL位于該service worker作用域內(nèi)的「任何打開的頁面」。具體來說,這些是WindowClient的實(shí)例。

圖片圖片

3.1 Service worker 在初始化時(shí)的生命周期

為了使service worker能夠控制頁面,首先必須將其部署。

讓我們看看一個(gè)沒有service worker的網(wǎng)站到部署全新service worker時(shí),中間發(fā)生了啥?

1. 注冊(Registration)

注冊是service worker生命周期的「初始步驟」:

<script>
  // 直到頁面完全加載后再注冊service worker
  window.addEventListener("load", () => {
    // 檢查service worker是否可用
    if ("serviceWorker" in navigator) {
      navigator.serviceWorker
        .register("/sw.js")
        .then(() => {
          console.log("Service worker 注冊成功!");
        })
        .catch((error) => {
          console.warn("注冊service worker時(shí)發(fā)生錯(cuò)誤:");
          console.warn(error);
        });
    }
  });
</script>

此代碼在「主線程」上運(yùn)行,并執(zhí)行以下操作:

  • 因?yàn)橛脩簟甘状卧L問網(wǎng)站時(shí)」沒有注冊service worker,所以等待「頁面完全加載后」再注冊一個(gè)。這樣可以避免在service worker預(yù)緩存任何內(nèi)容時(shí)出現(xiàn)「帶寬爭用」。
  • 盡管service worker得到了廣泛支持,但進(jìn)行「特性檢查」可以避免在不支持它的瀏覽器中出現(xiàn)錯(cuò)誤。
  • 當(dāng)頁面完全加載后,如果支持service worker,則注冊/sw.js。

還有一些關(guān)鍵要點(diǎn):

  • Service worker僅在HTTPS或localhost上可用。
  • 如果service worker的內(nèi)容包含「語法錯(cuò)誤」,注冊會(huì)失敗,并丟棄service worker。
  • service worker在一個(gè)作用域內(nèi)運(yùn)行。在這里,作用域是整個(gè)origin,因?yàn)樗菑母夸浖虞d的。
  • 當(dāng)注冊開始時(shí),service worker的狀態(tài)被設(shè)置為installing。

一旦注冊完成,「安裝」就開始了。

2. 安裝(Installation)

service worker在注冊后觸發(fā)其install事件。install「只會(huì)在每個(gè)service worker中調(diào)用一次,直到它被更新才會(huì)再次觸發(fā)」??梢允褂胊ddEventListener在worker的作用域內(nèi)注冊install事件的回調(diào):

// /sw.js
self.addEventListener("install", (event) => {
  const cacheKey = "前端柒八九_(tái)v1";

  event.waitUntil(
    caches.open(cacheKey).then((cache) => {
      // 將數(shù)組中的所有資產(chǎn)添加到'前端柒八九_(tái)v1'的`Cache`實(shí)例中以供以后使用。
      return cache.addAll([
        "/css/global.bc7b80b7.css",
        "/css/home.fe5d0b23.css",
        "/js/home.d3cc4ba4.js",
        "/js/A.43ca4933.js",
      ]);
    })
  );
});

這會(huì)創(chuàng)建一個(gè)新的Cache實(shí)例并對(duì)資產(chǎn)進(jìn)行「預(yù)緩存」。其中有一個(gè)event.waitUntil。event.waitUntil接受一個(gè)Promise,并等待該P(yáng)romise被解決。

在這個(gè)示例中,這個(gè)Promise執(zhí)行兩個(gè)異步操作:

  • 創(chuàng)建一個(gè)名為前端柒八九_(tái)v1的新Cache實(shí)例。
  • 在創(chuàng)建緩存之后,使用其異步的addAll方法「預(yù)緩存」一個(gè)資源URL數(shù)組。

如果傳遞給event.waitUntil的Promise被「拒絕,安裝將失敗」。如果發(fā)生這種情況,service worker將被「丟棄」。

如果Promise被解決,安裝成功,service worker的狀態(tài)將更改為installed,然后進(jìn)入「激活」階段。

3. 激活(Activation)

如果注冊和安裝成功,service worker將被「激活」,其狀態(tài)將變?yōu)閍ctivating。在service worker的activate事件中可以進(jìn)行激活期間的工作。在此事件中的一個(gè)典型任務(wù)是「清理舊緩存」,但對(duì)于「全新 service worker」,目前還不相關(guān)。

對(duì)于新的service worker,「安裝成功后,激活會(huì)立即觸發(fā)」。一旦激活完成,service worker的狀態(tài)將變?yōu)閍ctivated。

默認(rèn)情況下,新的service worker直到「下一次導(dǎo)航或頁面刷新之前才會(huì)開始控制頁面」。

3.2 處理 service worker 的更新

一旦部署了第一個(gè)service worker,它很可能需要在以后進(jìn)行更新。例如,如果請(qǐng)求處理或預(yù)緩存邏輯發(fā)生了變化,就可能需要進(jìn)行更新。

更新發(fā)生的時(shí)機(jī)

瀏覽器會(huì)在以下情況下檢查service worker的更新:

  1. 用戶導(dǎo)航到service worker作用域內(nèi)的頁面。
  2. 調(diào)用navigator.serviceWorker.register()并「傳入與當(dāng)前安裝的 service worker 不同的 URL」
  3. 調(diào)用navigator.serviceWorker.register()并「傳入與已安裝的 service worker 相同的 URL」,但具有「不同的作用域」。

更新的方式

了解瀏覽器何時(shí)更新service worker很重要,但“如何”也很重要。假設(shè)service worker的URL或作用域未更改,「只有在其內(nèi)容發(fā)生變化時(shí),當(dāng)前安裝的service worker才會(huì)更新到新版本」。

瀏覽器以幾種方式檢測變化:

  1. importScripts請(qǐng)求的腳本的「字節(jié)級(jí)更改」。
  2. service worker的「頂級(jí)代碼的任何更改」,這會(huì)影響瀏覽器生成的指紋。

為確保瀏覽器能夠可靠地檢測service worker內(nèi)容的變化,「不要使用 HTTP 緩存保留它,也不要更改其文件名」。當(dāng)導(dǎo)航到service worker作用域內(nèi)的新頁面時(shí),瀏覽器會(huì)自動(dòng)執(zhí)行更新檢查。

手動(dòng)觸發(fā)更新檢查

關(guān)于更新,注冊邏輯通常不應(yīng)更改。然而,一個(gè)例外情況可能是「網(wǎng)站上的會(huì)話持續(xù)時(shí)間很長」。這可能在「單頁應(yīng)用程序」中發(fā)生,因?yàn)閷?dǎo)航請(qǐng)求通常很少,應(yīng)用程序通常在應(yīng)用程序生命周期的開始遇到一個(gè)導(dǎo)航請(qǐng)求。在這種情況下,可以在「主線程上手動(dòng)觸發(fā)更新」:

navigator.serviceWorker.ready.then((registration) => {
  registration.update();
});

對(duì)于傳統(tǒng)的網(wǎng)站,或者在用戶會(huì)話不持續(xù)很長時(shí)間的任何情況下,手動(dòng)更新可能不是必要的。

安裝(Installation)

當(dāng)使用打包工具生成「靜態(tài)資源」時(shí),這些資源的「名稱中會(huì)包含哈希值」,例如framework.3defa9d2.js。假設(shè)其中一些資源被預(yù)緩存以供以后離線訪問,這將需要對(duì)service worker進(jìn)行更新以預(yù)緩存新的資源:

self.addEventListener("install", (event) => {
  const cacheKey = "前端柒八九_(tái)v2";

  event.waitUntil(
    caches.open(cacheKey).then((cache) => {
      // 將數(shù)組中的所有資產(chǎn)添加到'前端柒八九_(tái)v2'的`Cache`實(shí)例中以供以后使用。
      return cache.addAll([
        "/css/global.ced4aef2.css",
        "/css/home.cbe409ad.css",
        "/js/home.109defa4.js",
        "/js/A.38caf32d.js",
      ]);
    })
  );
});

與之前的install事件示例有兩個(gè)方面不同:

  1. 創(chuàng)建了一個(gè)具有 key 為前端柒八九_(tái)v2的「新 Cache 實(shí)例」。
  2. 預(yù)緩存資源的名稱已更改。(/css/global.bc7b80b7.css變?yōu)?css/global.ced4aef2.css)

更新后的service worker會(huì)與先前的service worker并存。這意味著舊的service worker仍然控制著任何打開的頁面。剛才安裝的新的service worker進(jìn)入等待狀態(tài),直到被激活。

默認(rèn)情況下,新的service worker將在「沒有任何客戶端由舊的service worker控制時(shí)激活」。這發(fā)生在相關(guān)網(wǎng)站的所有打開標(biāo)簽都關(guān)閉時(shí)。

激活(Activation)

當(dāng)安裝了新的service worker并結(jié)束了等待階段時(shí),它會(huì)被激活,并丟棄舊的service worker。在更新后的service worker的activate事件中執(zhí)行的常見任務(wù)是「清理舊緩存」。通過使用caches.keys獲取所有打開的 Cache 實(shí)例的key,并使用caches.delete刪除不在允許列表中的所有舊緩存:

self.addEventListener("activate", (event) => {
  // 指定允許的緩存密鑰
  const cacheAllowList = ["前端柒八九_(tái)v2"];

  // 獲取當(dāng)前活動(dòng)的所有`Cache`實(shí)例。
  event.waitUntil(
    caches.keys().then((keys) => {
      // 刪除不在允許列表中的所有緩存:
      return Promise.all(
        keys.map((key) => {
          if (!cacheAllowList.includes(key)) {
            return caches.delete(key);
          }
        })
      );
    })
  );
});


舊的緩存不會(huì)自動(dòng)清理。我們需要自己來做,否則可能會(huì)超過存儲(chǔ)配額。

由于第一個(gè)service worker中的前端柒八九_(tái)v1已經(jīng)過時(shí),緩存允許列表已更新為指定前端柒八九_(tái)v2,這將刪除具有不同名稱的緩存。

「激活事件在舊緩存被刪除后完成」。此時(shí),新的service worker將控制頁面,最終替代舊的service worker!

4. Service worker 緩存策略

要有效使用service worker,有必要采用一個(gè)或多個(gè)緩存策略,這需要對(duì)Cache API有一定的了解。

緩存策略是service worker的fetch事件與Cache API之間的交互。如何編寫緩存策略取決于不同情況。

普通的 Fetch 事件

緩存策略的另一個(gè)重要的用途就是與service worker的fetch事件配合使用。我們已經(jīng)聽說過一些關(guān)于「攔截網(wǎng)絡(luò)請(qǐng)求」的內(nèi)容,而service worker內(nèi)部的fetch事件就是處理這種情況的:

// 建立緩存名稱
const cacheName = "前端柒八九_(tái)v1";

self.addEventListener("install", (event) => {
  event.waitUntil(caches.open(cacheName));
});

self.addEventListener("fetch", async (event) => {
  // 這是一個(gè)圖片請(qǐng)求
  if (event.request.destination === "image") {
    // 打開緩存
    event.respondWith(
      caches.open(cacheName).then((cache) => {
        // 從緩存中響應(yīng)圖片,如果緩存中沒有,就從網(wǎng)絡(luò)獲取圖片
        return cache.match(event.request).then((cachedResponse) => {
          return (
            cachedResponse ||
            fetch(event.request.url).then((fetchedResponse) => {
              // 將網(wǎng)絡(luò)響應(yīng)添加到緩存以供將來訪問。
              // 注意:我們需要復(fù)制響應(yīng)以保存在緩存中,同時(shí)使用原始響應(yīng)作為請(qǐng)求的響應(yīng)。
              cache.put(event.request, fetchedResponse.clone());

              // 返回網(wǎng)絡(luò)響應(yīng)
              return fetchedResponse;
            })
          );
        });
      })
    );
  } else {
    return;
  }
});

上面的代碼執(zhí)行以下操作:

  • 檢查請(qǐng)求的destination屬性,以查看是否是圖像請(qǐng)求。
  • 如果圖像在service worker緩存中,則從緩存中提供它。如果沒有,從網(wǎng)絡(luò)獲取圖像,將響應(yīng)存儲(chǔ)在緩存中,并返回網(wǎng)絡(luò)響應(yīng)。
  • 所有其他請(qǐng)求都會(huì)通過service worker,不與緩存互動(dòng)。

fetch事件的事件對(duì)象包含一個(gè)request屬性,其中包含一些有用的信息,可幫助我們識(shí)別每個(gè)請(qǐng)求的類型:

  • url,表示當(dāng)前由 fetch 事件處理的網(wǎng)絡(luò)請(qǐng)求的 URL。
  • method,表示請(qǐng)求方法(例如GET或POST)。
  • mode,描述請(qǐng)求的模式。通常使用值navigate來區(qū)分對(duì) HTML 文檔的請(qǐng)求與其他請(qǐng)求。
  • destination,以一種避免使用所請(qǐng)求資產(chǎn)的文件擴(kuò)展名的方式描述所請(qǐng)求內(nèi)容的類型。

「異步操作是關(guān)鍵」。我們還記得install事件提供了一個(gè)event.waitUntil方法,它接受一個(gè)promise,并在激活之前等待其解析。fetch事件提供了類似的event.respondWith方法,我們可以使用它來返回異步fetch請(qǐng)求的結(jié)果或Cache接口的match方法返回的響應(yīng)。

緩存策略

1. 僅緩存(Cache only)

展示了從頁面到service worker到緩存的流程。

「僅緩存」運(yùn)作方式:當(dāng)service worker控制頁面時(shí),「匹配的請(qǐng)求只會(huì)進(jìn)入緩存」。這意味著為了使該模式有效,「任何緩存的資源都需要在安裝時(shí)進(jìn)行預(yù)緩存」,而「這些資源在service worker更新之前將不會(huì)在緩存中進(jìn)行更新」。

// 建立緩存名稱
const cacheName = "前端柒八九_(tái)v1";

// 要預(yù)緩存的資產(chǎn)
const preCachedAssets = ["/A.jpg", "/B.jpg", "/C.jpg", "/D.jpg"];

self.addEventListener("install", (event) => {
  // 在安裝時(shí)預(yù)緩存資產(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) {
    // 從緩存中獲取預(yù)緩存的資產(chǎn)
    event.respondWith(
      caches.open(cacheName).then((cache) => {
        return cache.match(event.request.url);
      })
    );
  } else {
    // 轉(zhuǎn)到網(wǎng)絡(luò)
    return;
  }
});

在上面的示例中,數(shù)組中的資產(chǎn)在安裝時(shí)被預(yù)緩存。當(dāng)service worker處理fetch請(qǐng)求時(shí),我們「檢查fetch事件處理的請(qǐng)求 URL 是否在預(yù)緩存資產(chǎn)的數(shù)組中」。

  • 如果是,我們從緩存中獲取資源,并跳過網(wǎng)絡(luò)。
  • 其他請(qǐng)求將通過網(wǎng)絡(luò)傳遞,只經(jīng)過網(wǎng)絡(luò)。

2. 僅網(wǎng)絡(luò)(Network only)

圖片圖片

「僅網(wǎng)絡(luò)」的策略與「僅緩存」相反,它將請(qǐng)求通過service worker傳遞到網(wǎng)絡(luò),而「不與 service worker 緩存進(jìn)行任何交互」。這是一種「確保內(nèi)容新鮮度」的好策略,但其權(quán)衡是「當(dāng)用戶離線時(shí)將無法正常工作」。

要確保請(qǐng)求直接通過到網(wǎng)絡(luò),只需「不對(duì)匹配的請(qǐng)求調(diào)用 event.respondWith」。如果我們想更明確,可以在要傳遞到網(wǎng)絡(luò)的請(qǐng)求的fetch事件回調(diào)中加入一個(gè)空的return;。這就是「僅緩存」策略演示中對(duì)于未經(jīng)預(yù)緩存的請(qǐng)求所發(fā)生的情況。

3. 緩存優(yōu)先,備用網(wǎng)絡(luò)(Cache first, falling back to network)

圖片圖片

對(duì)于「匹配的請(qǐng)求」,流程如下:

  • 請(qǐng)求到達(dá)緩存。如果資產(chǎn)在緩存中,就從緩存中提供。
  • 如果請(qǐng)求不在緩存中,去訪問網(wǎng)絡(luò)。
  • 一旦網(wǎng)絡(luò)請(qǐng)求完成,將其添加到緩存,然后返回網(wǎng)絡(luò)響應(yīng)。
// 建立緩存名稱
const cacheName = "前端柒八九_(tái)v1";

self.addEventListener("fetch", (event) => {
  // 檢查這是否是一個(gè)圖像請(qǐng)求
  if (event.request.destination === "image") {
    event.respondWith(
      caches.open(cacheName).then((cache) => {
        // 首先從緩存中獲取
        return cache.match(event.request.url).then((cachedResponse) => {
          // 如果我們有緩存的響應(yīng),則返回緩存的響應(yīng)
          if (cachedResponse) {
            return cachedResponse;
          }

          // 否則,訪問網(wǎng)絡(luò)
          return fetch(event.request).then((fetchedResponse) => {
            // 將網(wǎng)絡(luò)響應(yīng)添加到緩存以供以后訪問
            cache.put(event.request, fetchedResponse.clone());

            // 返回網(wǎng)絡(luò)響應(yīng)
            return fetchedResponse;
          });
        });
      })
    );
  } else {
    return;
  }
});

盡管這個(gè)示例只涵蓋了圖像,但這是一個(gè)很好的范例,「適用于所有靜態(tài)資產(chǎn)」(如CSS、JavaScript、圖像和字體),「尤其是哈希版本的資產(chǎn)」。它「通過跳過 HTTP 緩存可能啟動(dòng)的任何與服務(wù)器的內(nèi)容新鮮度檢查,為不可變資產(chǎn)提供了速度提升」。更重要的是,「任何緩存的資產(chǎn)都將在離線時(shí)可用」。

4. 網(wǎng)絡(luò)優(yōu)先,備用緩存(Network first, falling back to cache)

它的含義就是:

  • 首先通過網(wǎng)絡(luò)請(qǐng)求資源,然后將響應(yīng)放入緩存。
  • 如果以后「離線了,就回退到緩存中的最新版本的響應(yīng)」。

這種策略對(duì)于HTML或 API 請(qǐng)求非常有用,當(dāng)在線時(shí),我們希望獲取資源的最新版本,但希望在離線時(shí)能夠訪問最新可用的版本。

// 建立緩存名稱
const cacheName = "前端柒八九_(tái)v1";

self.addEventListener("fetch", (event) => {
  // 檢查這是否是導(dǎo)航請(qǐng)求
  if (event.request.mode === "navigate") {
    // 打開緩存
    event.respondWith(
      caches.open(cacheName).then((cache) => {
        // 首先通過網(wǎng)絡(luò)請(qǐng)求
        return fetch(event.request.url)
          .then((fetchedResponse) => {
            cache.put(event.request, fetchedResponse.clone());

            return fetchedResponse;
          })
          .catch(() => {
            // 如果網(wǎng)絡(luò)不可用,從緩存中獲取
            return cache.match(event.request.url);
          });
      })
    );
  } else {
    return;
  }
});
  • 首先,訪問頁面。可能需要在將 HTML 響應(yīng)放入緩存之前重新加載。
  • 然后在開發(fā)者工具中,模擬離線連接,然后重新加載。
  • 最后一個(gè)可用版本將立即從緩存中提供。

在需要重視離線功能,但又需要平衡該功能與獲取一些標(biāo)記或 API 數(shù)據(jù)的最新版本的情況下,「網(wǎng)絡(luò)優(yōu)先,備用緩存」是一種實(shí)現(xiàn)這一目標(biāo)的可靠策略。

5. 陳舊時(shí)重新驗(yàn)(Stale-while-revalidate)

圖片圖片

「陳舊時(shí)重新驗(yàn)證」策略是其中最復(fù)雜的。該策略的過程「優(yōu)先考慮了資源的訪問速度」,同時(shí)在后臺(tái)保持其更新。該策略的工作流程如下:

  • 對(duì)于首次請(qǐng)求的資源,從網(wǎng)絡(luò)獲取,將其放入緩存,并返回網(wǎng)絡(luò)響應(yīng)。
  • 對(duì)于后續(xù)請(qǐng)求,首先從緩存中提供資源,然后在后臺(tái)重新從網(wǎng)絡(luò)請(qǐng)求并更新資源的緩存條目。
  • 對(duì)于以后的請(qǐng)求,我們將收到從網(wǎng)絡(luò)獲取并在前一步放入緩存的最新版本。

這是一個(gè)適用于「需要保持更新但不是絕對(duì)必要的資源」的策略,比如網(wǎng)站的頭像。它們會(huì)在用戶愿意更新時(shí)進(jìn)行更新,但不一定需要在每次請(qǐng)求時(shí)獲取最新版本。

// 建立緩存名稱
const cacheName = "前端柒八九_(tái)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;
  }
});

5. Service Worker 預(yù)緩存的陷阱

如果將預(yù)緩存「應(yīng)用于太多的資產(chǎn)」,或者如果Service Worker在頁面「完成加載關(guān)鍵資產(chǎn)之前」就注冊了,那么可能會(huì)遇到問題。

當(dāng)Service Worker在「安裝期間預(yù)緩存資產(chǎn)時(shí),將同時(shí)發(fā)起一個(gè)或多個(gè)網(wǎng)絡(luò)請(qǐng)求」。如果時(shí)機(jī)不合適,這可能會(huì)對(duì)用戶體驗(yàn)產(chǎn)生問題。即使時(shí)機(jī)剛剛好,如果未對(duì)預(yù)緩存資產(chǎn)的「數(shù)量進(jìn)行限制」,仍可能會(huì)浪費(fèi)數(shù)據(jù)。

一切都取決于時(shí)機(jī)

如果Service Worker預(yù)緩存任何內(nèi)容,那么它的注冊時(shí)機(jī)很重要。Service Worker通常使用內(nèi)聯(lián)的<script>元素注冊。這意味著 HTML 解析器可能在頁面的關(guān)鍵資產(chǎn)加載完成之前就發(fā)現(xiàn)了Service Worker的注冊代碼。

這是一個(gè)問題。Service Worker在最壞的情況下應(yīng)該對(duì)性能沒有不利影響,而不是使性能變差。為用戶著想,應(yīng)該在「頁面加載事件」觸發(fā)時(shí)注冊Service Worker。這減少了預(yù)緩存可能干擾加載頁面的關(guān)鍵資產(chǎn)的機(jī)會(huì),從而意味著頁面可以更快地實(shí)現(xiàn)交互,而無需處理后來可能不需要的資產(chǎn)的網(wǎng)絡(luò)請(qǐng)求。

if ("serviceWorker" in navigator) {
  window.addEventListener("load", function () {
    navigator.serviceWorker.register("/service-worker.js");
  });
}

考慮數(shù)據(jù)使用

無論時(shí)機(jī)如何,「預(yù)緩存都涉及發(fā)送網(wǎng)絡(luò)請(qǐng)求」。如果不謹(jǐn)慎地選擇要預(yù)緩存的資產(chǎn)清單,結(jié)果可能會(huì)浪費(fèi)一些數(shù)據(jù)。

「浪費(fèi)數(shù)據(jù)是預(yù)緩存的一個(gè)潛在代價(jià)」,但并非每個(gè)人都可以訪問快速的互聯(lián)網(wǎng)或無限的數(shù)據(jù)計(jì)劃!「在預(yù)緩存時(shí),應(yīng)考慮刪除特別大的資產(chǎn),并依賴于運(yùn)行時(shí)緩存來捕捉它們」,而不是進(jìn)行假設(shè)用戶都需要這些資源,從而全部都進(jìn)行緩存。

6. 改進(jìn)Service Worker開發(fā)體驗(yàn)

雖然Service Worker生命周期確保了可預(yù)測的安裝和更新過程,但它可能使本地開發(fā)與常規(guī)開發(fā)有些不同。

本地開發(fā)的異常情況

通常情況下,Service WorkerAPI 僅在通過 HTTPS 提供的頁面上可用,但是我們平時(shí)開發(fā)中,經(jīng)常是通過 localhost 提供的頁面進(jìn)行嚴(yán)重。

此時(shí),我們可以通過 chrome://flags/#unsafely-treat-insecure-origin-as-secure,并指定要將不安全的起源視為安全起源。

Service Worker開發(fā)輔助工具

迄今為止,測試Service Worker的最有效方法是依賴于無痕窗口,例如 Chrome 中的無痕窗口。每次打開無痕窗口時(shí),我們都是從頭開始的。沒有活動(dòng)的Service Worker,也沒有打開的緩存實(shí)例。這種測試的常規(guī)流程如下:

  1. 打開一個(gè)無痕瀏覽窗口。
  2. 轉(zhuǎn)到注冊了Service Worker的頁面。
  3. 驗(yàn)證Service Worker是否按我們的預(yù)期工作。
  4. 關(guān)閉無痕窗口。
  5. 重復(fù)。

通過這個(gè)過程,我們模擬了Service Worker的生命周期。

Chrome DevTools 應(yīng)用程序面板中提供的其他測試工具也可以幫助,盡管它們可能在某些方面修改了Service Worker的生命周期。

圖片圖片

應(yīng)用程序面板有一個(gè)名為Service Workers的面板,顯示了當(dāng)前頁面的活動(dòng)Service Worker。每個(gè)活動(dòng)Service Worker都可以手動(dòng)更新,甚至完全注銷。面板頂部還有三個(gè)開關(guān)按鈕,有助于開發(fā)。

  • Offline(離線):模擬離線條件。這有助于測試當(dāng)前是否有活動(dòng)Service Worker提供脫機(jī)內(nèi)容。
  • Update on reload(重新加載時(shí)更新):當(dāng)切換開啟時(shí),每次重新加載頁面時(shí)都會(huì)重新獲取并替換當(dāng)前的Service Worker。
  • Bypass for network(繞過網(wǎng)絡(luò)):切換開啟時(shí),會(huì)繞過Service Worker的 fetch 事件中的任何代碼,并始終從網(wǎng)絡(luò)獲取內(nèi)容。

這些開關(guān)非常有幫助,特別是Bypass for network,當(dāng)我們正在開發(fā)一個(gè)具有活動(dòng)Service Worker的項(xiàng)目時(shí),同時(shí)還希望確保體驗(yàn)在沒有Service Worker的情況下也能按預(yù)期工作。

強(qiáng)制刷新

當(dāng)在本地開發(fā)中使用活動(dòng)的Service Worker,而不需要更新后刷新或繞過網(wǎng)絡(luò)功能時(shí),按住 Shift 鍵并單擊刷新按鈕也非常有用。

這個(gè)操作的鍵盤變體涉及在 macOS 計(jì)算機(jī)上按住 Shift、Cmd 和 R 鍵。

這被稱為「強(qiáng)制刷新」,它繞過 HTTP 緩存以獲取網(wǎng)絡(luò)數(shù)據(jù)。當(dāng)Service Worker處于活動(dòng)狀態(tài)時(shí),強(qiáng)制刷新也將完全繞過Service Worker。

如果不確定特定緩存策略是否按預(yù)期工作,或者希望從網(wǎng)絡(luò)獲取所有內(nèi)容以比較有Service Worker和無Service Worker時(shí)的行為,這個(gè)功能非常有用。更好的是,這是一個(gè)規(guī)定的行為,因此所有支持Service Worker的瀏覽器都會(huì)觀察到它。

檢查緩存內(nèi)容

如果無法檢查緩存,就很難確定緩存策略是否按預(yù)期工作。Chrome DevTools 的應(yīng)用程序面板提供了一個(gè)子面板,用于檢查緩存實(shí)例的內(nèi)容。

在DevTools中檢查緩存在DevTools中檢查緩存


這個(gè)子面板通過提供以下功能來使Service Worker開發(fā)變得更容易:

  • 查看緩存實(shí)例的名稱。
  • 檢查緩存資產(chǎn)的響應(yīng)正文以及它們關(guān)聯(lián)的響應(yīng)標(biāo)頭。
  • 從緩存中清除一個(gè)或多個(gè)項(xiàng)目,甚至刪除整個(gè)緩存實(shí)例。

這個(gè)圖形用戶界面使檢查Service Worker緩存更容易,以查看項(xiàng)目是否已添加、更新或從Service Worker緩存中完全刪除。

模擬存儲(chǔ)配額

在擁有大量大型靜態(tài)資產(chǎn)(如高分辨率圖像)的網(wǎng)站中,可能會(huì)觸及存儲(chǔ)配額。當(dāng)這種情況發(fā)生時(shí),瀏覽器將從緩存中驅(qū)逐它認(rèn)為過時(shí)或值得犧牲以騰出空間以容納新資產(chǎn)的項(xiàng)目。

處理存儲(chǔ)配額應(yīng)該是Service Worker開發(fā)的一部分,而 Workbox 使這個(gè)過程比自行管理更簡單。不管是否使用 Workbox,模擬自定義存儲(chǔ)配額以測試緩存管理邏輯可能是一個(gè)不錯(cuò)的主意。

存儲(chǔ)使用查看器存儲(chǔ)使用查看器

Chrome DevTools 的 Application 面板中的存儲(chǔ)使用查看器。在這里,正在設(shè)置自定義存儲(chǔ)配額。

Chrome DevTools 的 Application 面板有一個(gè)存儲(chǔ)子面板,提供了有關(guān)頁面使用的當(dāng)前存儲(chǔ)配額的信息。它還允許指定以兆字節(jié)為單位的自定義配額。一旦生效,Chrome 將執(zhí)行自定義存儲(chǔ)配額以進(jìn)行測試。

這個(gè)子面板還包含一個(gè)清除站點(diǎn)數(shù)據(jù)按鈕以及一整套相關(guān)的復(fù)選框,用于在單擊按鈕時(shí)清除哪些內(nèi)容。其中包括任何打開的緩存實(shí)例,以及注銷控制頁面的任何活動(dòng)Service Worker的能力。

責(zé)任編輯:武曉燕 來源: 前端柒八九
相關(guān)推薦

2022-04-26 09:16:07

PWA線程生命周期

2021-06-29 10:50:40

Linux.NET命令

2021-12-24 10:39:33

軟件開發(fā) 技術(shù)

2021-04-15 18:44:15

2023-12-14 15:01:04

數(shù)字化轉(zhuǎn)型數(shù)據(jù)智能數(shù)字化

2025-05-28 04:00:00

AI人工智能大數(shù)據(jù)

2020-04-23 18:24:40

戴爾

2024-12-29 19:36:04

2017-10-18 15:28:08

Service WorPWA離線

2022-08-26 08:35:59

對(duì)象設(shè)計(jì)底層

2022-01-13 13:24:16

工具底層邏輯

2020-05-27 20:45:31

Redis底層數(shù)據(jù)

2022-11-15 08:10:23

SpringMyBatis底層

2025-04-27 09:52:49

2024-01-03 09:03:40

MySQL索引數(shù)據(jù)庫

2022-02-25 00:04:35

智能推薦產(chǎn)品

2024-04-10 10:47:46

底層邏輯SFunction

2019-11-11 15:52:15

KVMLinuxQemu

2023-09-25 15:13:57

數(shù)字化轉(zhuǎn)型

2022-05-26 00:58:56

安全市場產(chǎn)品
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)