日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Service Worker ——这应该是一个挺全面的整理

發布時間:2025/6/16 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Service Worker ——这应该是一个挺全面的整理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

我在之前有空的時候粗略學習了一下 Service Worker ;最近有空,所以花時間再去學習了下。我在這里整理了下,希望對大家有幫助。

同時,如果文章中有錯誤或者描述不當的地方,歡迎大家能夠幫我指正,謝謝。

PS:文章很長,含有大量示例代碼。大家可以慢慢看:)


介紹

作為一個比較新的技術,大家可以把 Service Worker 理解為一個介于客戶端和服務器之間的一個代理服務器。在 Service Worker 中我們可以做很多事情,比如攔截客戶端的請求、向客戶端發送消息、向服務器發起請求等等,其中最重要的作用之一就是離線資源緩存。

首先,作為一個新技術,我們需要關注的是它在不同瀏覽器的兼容性,下面是來自于 caniuse.com 的一張圖:

從這張圖中,我們可以看到 IE 和 Opera Mini 全面撲街,而主流瀏覽器中 Edge 17以下不支持,Safair 和 IOS Safair 剛剛開始支持,而火狐和 Chrome 支持良好。所以大家可以放心的使用,不過最好還是做一下判斷。

對于 Service Worker ,了解過 Web Worker 的同學可能會比較好理解。它和 Web Worker 相比,有相同的點,也有不同的地方。

相同:

  • Service Worker 工作在 worker context 中,是沒有訪問 DOM 的權限的,所以我們無法在 Service Worker 中獲取 DOM 節點,也無法在其中操作 DOM 元素;
  • 我們可以通過 postMessage 接口把數據傳遞給其他 JS 文件;
  • Service Worker 中運行的代碼不會被阻塞,也不會阻塞其他頁面的 JS 文件中的代碼;
  • 不同的地方在于,Service Worker 是一個瀏覽器中的進程而不是瀏覽器內核下的線程,因此它在被注冊安裝之后,能夠被在多個頁面中使用,也不會因為頁面的關閉而被銷毀。因此,Service Worker 很適合被用與多個頁面需要使用的復雜數據的計算——購買一次,全家“收益”。

    另外有一點需要注意的是,出于對安全問題的考慮,Service Worker 只能被使用在 https 或者本地的 localhost 環境下

    注冊安裝

    下面就讓我們來使用 Service Worker 。

    如果當前使用的瀏覽器支持 Service Worker ,則在 window.navigator 下會存在 serviceWorker 對象,我們可以使用這個對象的 register 方法來注冊一個 Service Worker。

    這里需要注意的一點是,Service Worker 在使用的過程中存在大量的 Promise ,對于 Promise 不是很了解的同學可以先去看一下相關文檔。 Service Worker 的注冊方法返回的也是一個 Promise 。

    // index.js if ('serviceWorker' in window.navigator) {navigator.serviceWorker.register('./sw.js', { scope: './' }).then(function (reg) {console.log('success', reg);}).catch(function (err) {console.log('fail', err);}); } 復制代碼

    在這段代碼中,我們先使用 if 判斷下瀏覽器是否支持 Service Worker ,避免由于瀏覽器不兼容導致的 bug 。

    register 方法接受兩個參數,第一個是 service worker 文件的路徑,請注意:這個文件路徑是相對于 Origin ,而不是當前 JS 文件的目錄的;第二個參數是 Serivce Worker 的配置項,可選填,其中比較重要的是 scope 屬性。按照文檔上描述,它是 Service Worker 控制的內容的子目錄,這個屬性所表示的路徑不能在 service worker 文件的路徑之上,默認是 Serivce Worker 文件所在的目錄;關于這個屬性,文檔中講的不是很清楚,我也有很多疑問,會在接下來的內容中提出。希望有知道的同學能幫我解惑。

    register 方法返回一個 Promise 。如果注冊失敗,可以通過 catch 來捕獲錯誤信息;如果注冊成功,可以使用 then 來獲取一個 ServiceWorkerRegistration 的實例,有興趣的同學可以去翻閱文檔。

    注冊完 Service Worker 之后,瀏覽器會為我們自動安裝它,因此我們就可以在 service worker 文件中監聽它的 install 事件了。

    // sw.js this.addEventListener('install', function (event) {console.log('Service Worker install'); }); 復制代碼

    同樣的,Service Worker 在安裝完成后會被激活,所以我們也可監聽 activate 事件。

    // sw.js this.addEventListener('activate', function (event) {console.log('Service Worker activate'); }); 復制代碼

    這時,我們可以在 Chorme 的開發者工具中看到我們注冊的 Service Worker。

    在默認情況下,Service Worker 必定會每24小時被下載一次,如果下載的文件是最新文件,那么它就會被重新注冊和安裝,但不會被激活,當不再有頁面使用舊的 Service Worker 的時候,它就會被激活。

    這對于我們開發來說是很不方便的,因此在這里我勾選了一個名為 Update on reload 的單選框,選中它之后,我們每次刷新頁面都能夠使用最新的 service worker 文件。

    在同一個 Origin 下,我們可以注冊多個 Service Worker。但是請注意,這些 Service Worker 所使用的 scope 必須是不相同的

    if ('serviceWorker' in window.navigator) {navigator.serviceWorker.register('./sw/sw.js', { scope: './sw' }).then(function (reg) {console.log('success', reg);})navigator.serviceWorker.register('./sw2/sw2.js', { scope: './sw2' }).then(function (reg) {console.log('success', reg);}) } 復制代碼

    信息通訊

    之前說過,使用 postMessage 方法可以進行 Service Worker 和頁面之間的通訊,下面就讓我們來試一下。

    從頁面到 Service Worker

    首先是從頁面發送信息到 Serivce Worker 。

    // index.js if ('serviceWorker' in window.navigator) {navigator.serviceWorker.register('./sw.js', { scope: './' }).then(function (reg) {console.log('success', reg);navigator.serviceWorker.controller && navigator.serviceWorker.controller.postMessage("this message is from page");}); } 復制代碼

    為了保證 Service Worker 能夠接收到信息,我們在它被注冊完成之后再發送信息,和普通的 window.postMessage 的使用方法不同,為了向 Service Worker 發送信息,我們要在 ServiceWorker 實例上調用 postMessage 方法,這里我們使用到的是 navigator.serviceWorker.controller 。

    // sw.js this.addEventListener('message', function (event) {console.log(event.data); // this message is from page }); 復制代碼

    在 service worker 文件中我們可以直接在 this 上綁定 message 事件,這樣就能夠接收到頁面發來的信息了。

    對于不同 scope 的多個 Service Worker ,我么也可以給指定的 Service Worker 發送信息。

    // index.js if ('serviceWorker' in window.navigator) {navigator.serviceWorker.register('./sw.js', { scope: './sw' }).then(function (reg) {console.log('success', reg);reg.active.postMessage("this message is from page, to sw");})navigator.serviceWorker.register('./sw2.js', { scope: './sw2' }).then(function (reg) {console.log('success', reg);reg.active.postMessage("this message is from page, to sw 2");}) }// sw.js this.addEventListener('message', function (event) {console.log(event.data); // this message is from page, to sw });// sw2.js this.addEventListener('message', function (event) {console.log(event.data); // this message is from page, to sw 2 }); 復制代碼

    請注意,當我們在注冊 Service Worker 時,如果使用的 scope 不是 Origin ,那么navigator.serviceWorker.controller 會為 null。這種情況下,我們可以使用 reg.active 這個對象下的 postMessage 方法,reg.active 就是被注冊后激活 Serivce Worker 實例。但是,由于 Service Worker 的激活是異步的,因此第一次注冊 Service Worker 的時候,Service Worker 不會被立刻激活, reg.active 為 null,系統會報錯。我采用的方式是返回一個 Promise ,在 Promise 內部進行輪詢,如果 Service Worker 已經被激活,則 resolve 。

    // index.js navigator.serviceWorker.register('./sw/sw.js').then(function (reg) {return new Promise((resolve, reject) => {const interval = setInterval(function () {if (reg.active) {clearInterval(interval);resolve(reg.active);}}, 100)})}).then(sw => {sw.postMessage("this message is from page, to sw");})navigator.serviceWorker.register('./sw2/sw2.js').then(function (reg) {return new Promise((resolve, reject) => {const interval = setInterval(function () {if (reg.active) {clearInterval(interval);resolve(reg.active);}}, 100)})}).then(sw => {sw.postMessage("this message is from page, to sw2");}) 復制代碼

    從 Service Worker 到頁面

    下一步就是從 Service Worker 發送信息到頁面了,不同于頁面向 Service Worker 發送信息,我們需要在 WindowClient 實例上調用 postMessage 方法才能達到目的。而在頁面的JS文件中,監聽 navigator.serviceWorker 的 message 事件即可收到信息。

    而最簡單的方法就是從頁面發送過來的消息中獲取 WindowClient 實例,使用的是 event.source ,不過這種方法只能向消息的來源頁面發送信息。

    // sw.js this.addEventListener('message', function (event) {event.source.postMessage('this message is from sw.js, to page'); });// index.js navigator.serviceWorker.addEventListener('message', function (e) {console.log(e.data); // this message is from sw.js, to page }); 復制代碼

    如果不想受到這個限制,則可以在 serivce worker 文件中使用 this.clients 來獲取其他的頁面,并發送消息。

    // sw.js this.clients.matchAll().then(client => {client[0].postMessage('this message is from sw.js, to page'); }) 復制代碼

    關于這個方法,我有一些沒有解決的疑問的。在我的試驗中,注冊 Service Worker 時候設置的 scope 的值會對獲取到的 client 產生影響。

    如果在注冊 Service Worker 的時候,把 scope 設置為非 origin 目錄,那么在 service worker 文件中,我無法獲取到 Origin 路徑對應頁面的 client。

    // index.js navigator.serviceWorker.register('./sw.js', { scope: './sw/' });// sw.js this.clients.matchAll().then(client => {console.log(client); // [] }) 復制代碼

    我查找了一些資料,但是沒有找到關于 scope 和 client 之間的聯系的明確說明文檔。我的猜測是,Service Worker 是否只能夠獲取到 scope 路徑下的子頁面的 client ,但是我使用 react router 試驗發現似乎又不是,希望有知道的同學能夠幫忙解答,謝謝!

    使用 Message Channel 來通信

    另外一種比較好用的通信方式是使用 Message Channel 。

    // index.js navigator.serviceWorker.register('./sw.js', { scope: './' }).then(function (reg) {const messageChannel = new MessageChannel();messageChannel.port1.onmessage = e => {console.log(e.data); // this message is from sw.js, to page}reg.active.postMessage("this message is from page, to sw", [messageChannel.por2]);})// sw.js this.addEventListener('message', function (event) {console.log(event.data); // this message is from page, to swevent.ports[0].postMessage('this message is from sw.js, to page'); }); 復制代碼

    使用這種方式能夠使得通道兩端之間可以相互通信,而不是只能向消息源發送信息。舉個例子,兩個 Service Worker 之間的通信。

    // index.jsconst messageChannel = new MessageChannel();navigator.serviceWorker.register('./sw/sw.js').then(function (reg) {console.log(reg)return new Promise((resolve, reject) => {const interval = setInterval(function () {if (reg.active) {clearInterval(interval);resolve(reg.active);}}, 100)})}).then(sw => {sw.postMessage("this message is from page, to sw", [messageChannel.port1]);})navigator.serviceWorker.register('./sw2/sw2.js').then(function (reg) {return new Promise((resolve, reject) => {const interval = setInterval(function () {if (reg.active) {clearInterval(interval);resolve(reg.active);}}, 100)})}).then(sw => {sw.postMessage("this message is from page, to sw2", [messageChannel.port2]);})// sw.js this.addEventListener('message', function (event) {console.log(event.data); // this message is from page, to swevent.ports[0].onmessage = e => {console.log('sw:', e.data); // sw: this message is from sw2.js}event.ports[0].postMessage('this message is from sw.js'); });// sw2.js this.addEventListener('message', function (event) {console.log(event.data); // this message is from page, to sw2event.ports[0].onmessage = e => {console.log('sw2:', e.data); // sw2: this message is from sw.js}event.ports[0].postMessage('this message is from sw2.js'); }); 復制代碼

    首先讓頁面給兩個 Service Worker 發送信息,并且把信息通道的端口發送過去;然后在兩個 service worker 文件中使用端口分別設置接受信息的回調函數,之后它們就能夠互相發送信息并接收到來自通道對面的消息了。

    靜態資源緩存

    下面要講的就是重頭戲,也是 Service Worker 能夠實現的最主要的功能——靜態資源緩存。

    正常情況下,用戶打開網頁,瀏覽器會自動下載網頁所需要的 JS 文件、圖片等靜態資源。我們可以通過 Chrome 開發工具的 Network 選項來查看。

    但是如果用戶在沒有聯網的情況下打開網頁,瀏覽器就無法下載這些展示頁面效果所必須的資源,頁面也就無法正常的展示出來。

    我們可以使用 Service Worker 配合 CacheStroage 來實現對靜態資源的緩存。

    緩存指定靜態資源

    // sw.js this.addEventListener('install', function (event) {console.log('install');event.waitUntil(caches.open('sw_demo').then(function (cache) {return cache.addAll(['/style.css','/panda.jpg','./main.js'])})); }); 復制代碼

    當 Service Worker 在被安裝的時候,我們能夠對制定路徑的資源進行緩存。CacheStroage 在瀏覽器中的接口名是 caches ,我們使用 caches.open 方法新建或打開一個已存在的緩存;cache.addAll 方法的作用是請求指定鏈接的資源并把它們存儲到之前打開的緩存中。由于資源的下載、緩存是異步行為,所以我們要使用事件對象提供的 event.waitUntil 方法,它能夠保證資源被緩存完成前 Service Worker 不會被安裝完成,避免發生錯誤。

    從 Chrome 開發工具中的 Application 的 Cache Strogae 中可以看到我們緩存的資源。

    這種方法只能緩存指定的資源,無疑是不實用的,所以我們需要針對用戶發起的每一個請求進行緩存。

    動態緩存靜態資源

    this.addEventListener('fetch', function (event) {console.log(event.request.url);event.respondWith(caches.match(event.request).then(res => {return res ||fetch(event.request).then(responese => {const responeseClone = responese.clone();caches.open('sw_demo').then(cache => {cache.put(event.request, responeseClone);})return responese;}).catch(err => {console.log(err);});})) }); 復制代碼

    我們需要監聽 fetch 事件,每當用戶向服務器發起請求的時候這個事件就會被觸發。有一點需要注意,頁面的路徑不能大于 Service Worker 的 scope,不然 fetch 事件是無法被觸發的。

    在回掉函數中我們使用事件對象提供的 respondWith 方法,它可以劫持用戶發出的 http 請求,并把一個 Promise 作為響應結果返回給用戶。然后我們使用用戶的請求對 Cache Stroage 進行匹配,如果匹配成功,則返回存儲在緩存中的資源;如果匹配失敗,則向服務器請求資源返回給用戶,并使用 cache.put 方法把這些新的資源存儲在緩存中。因為請求和響應流只能被讀取一次,所以我們要使用 clone 方法復制一份存儲到緩存中,而原版則會被返回給用戶

    在這里有幾點需要注意:

  • 當用戶第一次訪問頁面的時候,資源的請求是早于 Service Worker 的安裝的,所以靜態資源是無法緩存的;只有當 Service Worker 安裝完畢,用戶第二次訪問頁面的時候,這些資源才會被緩存起來;
  • Cache Stroage 只能緩存靜態資源,所以它只能緩存用戶的 GET 請求;
  • Cache Stroage 中的緩存不會過期,但是瀏覽器對它的大小是有限制的,所以需要我們定期進行清理;
  • 對于用戶發起的 POST 請求,我們也可以在攔截后,通過判斷請求中攜帶的 body 的內容來進行有選擇的返回。

    if(event.request.method === 'POST') {event.respondWith(new Promise(resolve => {event.request.json().then(body => {console.log(body); // 用戶請求攜帶的內容})resolve(new Response({ a: 2 })); // 返回的響應}))} } 復制代碼

    我們可以在 fetch 事件的回掉函數中對請求的 method 、url 等各項屬性進行判斷,選擇不同的操作。

    對于靜態資源的緩存,Cache Stroage 是個不錯的選擇;而對于數據,我們可以使用 IndexedDB 來存儲,同樣是攔截用戶請求后,使用緩存在 IndexDB 中的數據作為響應返回,詳細的內容我就不在這里講了,有興趣的同學可以自己去了解下。

    更新 Cache Stroage

    前面提到過,當有新的 service worker 文件存在的時候,他會被注冊和安裝,等待使用舊版本的頁面全部被關閉后,才會被激活。這時候,我們就需要清理下我們的 Cache Stroage 了,刪除舊版本的 Cache Stroage 。

    this.addEventListener('install', function (event) {console.log('install');event.waitUntil(caches.open('sw_demo_v2').then(function (cache) { // 更換 Cache Stroagereturn cache.addAll(['/style.css','/panda.jpg','./main.js'])})) });const cacheNames = ['sw_demo_v2']; // Cahce Stroage 白名單this.addEventListener('activate', function (event) {event.waitUntil(caches.keys().then(keys => {return Promise.all[keys.map(key => {if (!cacheNames.includes(key)) {console.log(key);return caches.delete(key); // 刪除不在白名單中的 Cache Stroage}})]})) }); 復制代碼

    首先在安裝 Service Worker 的時候,要換一個 Cache Stroage 來存儲,然后設置一個白名單,當 Service Worker 被激活的時候,將不在白名單中的 Cache Stroage 刪除,釋放存儲空間。同樣使用 event.waitUntil ,在 Service Worker 被激活前執行完刪除操作。

    小結

    Service Worker 作為一個新的技術,在靜態資源緩存和處理多頁面所需的復雜數據等方面都有很不錯的應用前景。作為實現 PWA 不可或缺的一部分,我相信,不管是他的瀏覽器兼容性、功能的多樣性以及文檔的完整性,都會變的越來越好。

    同時,肯定還有很多我沒有學到、講到,或者是我忽略了 Service Worker 的內容存在,所以我希望可以和大家一起學習,特別是 scope 這個屬性,希望有知道的同學幫我答疑解惑,謝謝。

    轉載于:https://juejin.im/post/5b06a7b3f265da0dd8567513

    總結

    以上是生活随笔為你收集整理的Service Worker ——这应该是一个挺全面的整理的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。