Service Worker 從入門到出門

快狗叫車前端團隊發表於2019-07-11

前言

正常的開場本應該是“什麼是Service Worker”,但Service Worker往往會和PWA被一同提起。無論螢幕前的你是有豐富PWA開發經驗的大佬,還是從沒有聽過這個概念的小佬,本文都有義務講清楚Service Worker和PWA的關係,在此假設大家僅僅是一個對Service Worker感興趣的前端工程師,並沒有豐富的相關經驗。
另外,本文力求抓住重點,理清思路,並不是一篇偏技術流的文章,如果您只是想了解某些API怎麼用,或者遇到了什麼問題需要解決,那本文將浪費您5-20分鐘的時間,點個贊之後就快快關掉吧~

從PWA說起

PWA全稱Progressive Web Apps,直譯過來是“漸進式網路應用程式”,看到這個翻譯,大多數人應該是不接受的,因為我們並不能從字面上理解PWA是什麼。以下是維基百科對PWA的定義:

漸進式網路應用程式(英語:Progressive Web Apps,簡稱:PWA)是一種普通網頁或網站架構起來的網路應用程式,但它可以以傳統應用程式或原生移動應用程式的形式展示給使用者。

這也是一個正確但不充分的定義,不能很好描述PWA的真實特性。

經過一段時間的整理,在此表達一下本文對PWA的理解:
Web和App都懂,但Progressive是幾個意思?說起“Progressive 漸進式”,想必大家或多或少聽過一些關於Web應用“平穩退化、漸進增強”的設計理念,由於瀏覽器對於Web標準的跟進會有不同程度的滯後(更有甚者不但不跟進還要亂搞),很多優秀的新特性老舊瀏覽器並不支援,所以開發者有時會採取漸進式的策略,充分利用新特性,為支援新特性的瀏覽器提供更完善的功能和更好的體驗( 讓一部分人先富起來?)。PWA之P,大約就是這個意思。
眾所周知,Web應用和Native應用原本井水不犯河水,二者有著各自的應用場景和優勢。但隨著浮誇的移動網際網路時代的到來,貪婪的人類想要取長補短,兼顧二者的優點,樂此不疲地發明了一坨又一坨血統不純的爛尾混合開發技術,在此不一一列舉,大家都懂。PWA是為了達到同樣目的的另一種嘗試,它絕不是革命性的技術,只是傳統Web應用向Native應用的又一次瘋狂試探,也只是一次不大不小的進化而已。但區別於混合開發技術,PWA是血統純正的Web技術的自然延伸,背後有相關Web標準支撐。

這一次,PWA相對於傳統Web應用,主要在以下幾個方面變得更強:

  • 觀感方面:在手機上,可以新增Web應用到桌面,並可提供類似於Native應用的沉浸式體驗(翻譯成人話就是可以隱藏瀏覽器的腦門)。這部分背後的技術是manifest,雖然可以給帶來全新的體驗,但不是本文重點,就此別過。
  • 效能方面:由於本文主角Service Worker具有攔截瀏覽器HTTP請求的超能力,搭配CacheStorage,PWA可以提升Web應用在網路條件不佳甚至離線時的使用者體驗和效能。
  • 其它方面:推送通知、後臺同步等可選的高階功能,這些功能也是利用Service Worker來實現的。

簡單總結:PWA是Web應用的自然進化,Service Worker是PWA的關鍵。

目前來看,本文已疑似離題,所以讓我們趕快回到Service Worker

Service Worker是瀏覽器在後臺獨立於網頁執行的、用JavaScript編寫的指令碼。
讓我們來看看最小的Service Worker長什麼樣,以及怎麼跑起來:(朋友們請不要看見程式碼塊就自動略過,請相信我真的沒有幾行)

// 不起眼的一行if,除了防止報錯之外,也無意間解釋了PWA的P:
// 如果瀏覽器不支援Service Worker,那就當什麼都沒有發生過
if ('serviceWorker' in navigator) {
    window.addEventListener('load', function () {
        // 所以Service Worker只是一個掛在navigator物件上的HTML5 API而已
        navigator.serviceWorker.register('/service-worker.js').then(function (registration) {
            console.log('我註冊成功了666');
        }, function (err) {
            console.log('我註冊失敗了');
        });
    });
}
複製程式碼

以上程式碼,在load事件觸發後,下載並註冊了service-worker.js這個檔案,Service Worker的邏輯,就寫在這裡:

// service-worker.js
// 雖然可以在裡邊為所欲為地寫任何js程式碼,或者也可以什麼都不寫,
// 都不妨礙這是一個Service Worker,但還是舉一個微小的例子:
self.addEventListener('fetch', function (event) {
    if (/\.png$/.test(event.request.url)) {
        event.respondWith(fetch('/images/支付寶收款碼.png'));
    }
});
複製程式碼

受到身後TL大哥桌面上掃碼提需求支付寶二維碼的啟發,以上程式碼,可以攔截網頁上所有png圖片的請求,返回你的支付寶收款碼圖片,只要使用者夠多,總會有人給你打錢的。
程式碼中的self是第一個匪夷所思的地方,看起來是一個未定義的變數,但稍加思索我們就可以意識到這是一個關鍵字,類似於window或global,代表該Service Worker自身,所以想要玩轉Service Worker,是需要學習它的API的。
我們暫且不去仔細看那些需要背的API,只需要記得這段程式碼做了什麼,它像一個middleware一樣,攔截並處理了HTTP請求,此時的Service Worker,可以理解為一個客戶端代理。由於程式碼是人為編寫的,開啟了無限可能。另外,最可怕的就是流氓會武術了,所以Service Worker要求HTTPS,注意那個"S",但為了開發除錯方便,localhost除外。

簡單總結:

  • 我們需要手動編寫service-worker.js檔案。
  • 我們需要在網頁中下載並註冊service-worker.js檔案。
  • Service Worker具有超能力,可以攔截並處理HTTP請求。

Service Worker的生命週期

按照一篇文章的一貫節奏,到這個該深入的階段,閱讀體驗往往突然變差,為了避免這種情況,不得不提的Service Worker生命週期話題,推薦大家去其它地方閱讀,這種文章已經很多了,本文作者認為,此處可以寫但沒必要,並且沒有信心寫得比它們好,寫了反而有抄襲湊字之嫌,所以在此僅僅貼出MDN的圖,並簡單總結(微笑)。

image

簡單總結:

  • Service Worker生命週期:安裝中、安裝後、啟用中、啟用後、我廢了。(有點像元件的生命週期不是嗎?)
  • 首次導航到網站時,會下載、解析並執行Service Worker檔案,觸發install事件,嘗試安裝Service Worker,如果install事件回撥函式中的操作都執行成功,標誌Service Worker安裝成功,此時進入waiting狀態,注意這時的Service Worker只是準備好了而已,並沒有生效,當使用者二次進入網站時,才會啟用Service Worker,此時會觸發activate事件,標誌Service Worker正式啟動,開始響應fetch、post、sync等事件。

Service Worker的主要事件

  • install:顧名思義,Service Worker安裝時觸發,通常在這個時機快取檔案。
  • activate:顧名思義,Service Worker啟用時觸發,通常在這個時機做一些重置的操作,例如處理舊版本Service Worker的快取。
  • fetch:顧名思義,瀏覽器發起HTTP請求時觸發,通常在這個事件的回撥函式中匹配快取,是最常用的事件。
  • push:顧名思義,和推送通知功能相關,沒有相關需求的話可以不去了解。
  • sync:顧名思義,和後臺同步功能相關,沒有相關需求的話可以不去了解。

Service Worker的應用

當我們掌握了Service Worker的基礎,就可以嘗試著應用了,在此著重介紹一些關乎網頁效能方面的應用,不會貼上程式碼,而主要談談思路和方法。

1.快取靜態資源

Service Worker的一大應用是可以利用CacheStorage API來快取js、css、字型、圖片等靜態檔案。我們可以在Service Worker的install階段,指定需要快取的具體檔案,在fetch事件的回撥函式中,檢查請求的url,如果匹配了已快取的資源,則不再從服務端獲取,以此達到提升網頁效能的目的。常用的構建PWA的App Shell架構,就是利用這種方式實現的。
需要注意的是,效能的提升是相對於完全沒有快取的情況來講的,而瀏覽器本身有著相對完善的HTTP快取機制。所以使用Service Worker快取,並不能使我們已經相對完善的架構有立竿見影的效能提升,Service Worker快取真正有意義的地方在於,利用它可以更精準地、以編碼方式控制快取,如何快取、快取什麼、如何更新快取,完全取決於程式碼如何寫,所以這提供了很大的自由度,但同時也帶來維護成本。它只是換了一種快取方式,而不是從無到有的突破。

2.離線體驗

上一部分我們只是快取了js、css、字型、圖片等靜態資源,但如果我們將首頁index.html也快取呢?那結果是我們的網頁甚至可以支援離線瀏覽。
聽起來很棒是吧?請坐下,這裡有一個巨大的問題:假定我們的主頁是index.html,裡邊註冊了service-worker.js,service-worker.js中快取了index.html以達到離線瀏覽的目的,那麼問題來了,在我們下次上線,死活是不會生效的,因為使用者訪問的總是快取過的index.html,是不是很尷尬?我們需要更新service-worker.js來重新快取index.html,雖然網上也有一些方案解決這種問題,但似乎異常糾結,讓我們難免懷疑離線瀏覽是否有意義。
下面的做法似乎更好一些:既然我們現在具有了離線快取檔案和攔截HTTP請求的能力,那我們可以在Service Worker安裝時,快取一個offline.html,類似於404頁面。離線狀態下,我們訪問index.html檔案的請求是會失敗的,在這個時機,我們返回offline.html檔案展示給使用者,至於具體展示些什麼,取決於具體需求,甚至可以像chrome離線時那樣,做一個小遊戲來調戲沒有網路的使用者。

image

3.其它

Service Worker只是提供了一些厲害的功能,但如何應用完全取決於開發者,如果腦洞大開,完全有可能提供令人耳目一新的體驗。在此僅僅舉一個小例子:
我們知道,網頁中圖片是很消耗頻寬資源的,使用者等待網站載入,很多時候都是在等圖片,而大多數放在CDN上的圖片,都支援新增字尾引數獲取不同解析度照片的功能。假設我們有辦法知道一個使用者的網路條件的好壞(至於如何判斷一個使用者的網路條件,是另外一件事,可以讓使用者選擇,也可用技術手段解決),把使用者分級,暫且分為兩級:網速快的和網速慢的。我們把網速級別資訊放到HTTP請求的header中(或其它你想得到的合適的地方),當發起圖片請求的時候,我們有機會拿到使用者的網路級別,如果是網速快的使用者,我們通過字尾引數返回CDN上高解析度的圖片,反之相反。
這樣的結果是網速快的使用者可以看到更清晰的照片,而網速慢的使用者雖然看到的照片清晰度差,但可以更早地看到照片,不必經過漫長的等待。

注意事項

以上的討論,都是紙上談兵,當我們真的要在生產環境實際應用的時候,仔細想想,就會發現事情不是那麼簡單,在此列舉一些需要注意和考慮的地方。

  • 首先,我們要做的是買好後悔藥。因為無論如何,Service Worker是在千奇百怪的客戶端做一些危險的操作,如果出現問題(肯定會出現問題),往往不是bug,而是事故級別。那樣的話,我們恐怕需要在另一家公司才能繼續試驗我們可愛的Service Worker了。想的到的辦法是,在類似配置中心的地方,或服務端,提供開關介面,請求頁面前呼叫介面,如果發現事情不妙,就關閉開關,觸發銷燬Service Worker的程式碼。
  • 如果專案經過了迭代,那麼在專案測試的時候,QA同學的瀏覽器環境和真實使用者的瀏覽器環境是不同的,至少是覆蓋不全的。Service Worker的存在,本質上把原本就有狀態的客戶端變成了狀態更加複雜的客戶端。所以可能導致一些bug無法在測試階段被發現,而且目測這些bug極難復現、除錯、解決和檢驗。

應用場景

接下來讓我們來談談Service Worker的應用場景,或者說什麼樣的情況才需要上Service Worker,這很好理解,但很重要,我們不能拿著錘子,看什麼都像釘子。

  • 網站功能趨於穩定:頻繁迭代的網站似乎不方便加Service Worker。
  • 網站需要擁有大量使用者:管理後臺、OA系統等場景似乎不是很有必要加Service Worker。
  • 網站真的在追求使用者體驗:Bug多多、臉不好看的網站似乎不是很有必要加Service Worker。
  • 網站使用者體驗關乎使用者留存:12306似乎完全不需要加Service Worker。
  • 等等等等。。。
    image

簡單總結:Service Worker的初衷是極致優化使用者體驗,是用來錦上添花的,技術只是技術,但實際應用前,應考慮成本和收益。

一些廢話

本文簡單談了談PWA和Service Worker的關係、Service Worker的基礎、Service Worker的生命週期和事件、Service Worker的簡單應用,以及實際應用中的注意事項和場景,只是一篇入門級的科普文章,大家可根據自己感興趣的具體方面,去深入瞭解。鑑於本文作者是個才疏學淺的噴子,文章難免有所疏漏甚至誤導,歡迎指正。

另外在碼字過程中,有一些感想在此和大家分享:

  • Service Worker和PWA已經出現大約4年左右了,但似乎一直是叫好不叫座的狀態,也看不到什麼爆發的趨勢,其原因可能是多方面的,但最大的原因,可能是沒有巨大的需求來推動,真正有潛力的技術,一定是你覺得不學都混不下去的那種。
  • Web應用和Native應用的本質區別,是編譯產物放在服務端還是客戶端。Web應用的靜態資源需要從服務端獲取,是造成了Web應用體驗差的一大原因。瀏覽器HTTP快取,和Service Worker的快取,都是折中的方案,至少解決不了首屏載入慢的問題。
  • 眼看著5G時代就要到來,Web應用的效能問題,是否還是問題?Service Worker可能帶來的那幾百毫秒級的效能提升,是否還有意義?當大多數人的網路條件上了新的臺階之後,可能網頁將不再卡那麼幾秒,APP也可以瞬間下載完成,那時我們真的還會折騰快取嗎?網頁和APP還都會存在嗎?

事已至此,本文已離題無誤,至於Service Worker,用來吹牛實在是再合適不過了,如果真要大規模應用,大家可能需要結合具體情況綜合考慮。但無論如何,作為一項技術儲備還是有必要的。不說了,外賣涼了。那你還愣著幹嘛?快趕快點贊評論加收藏啊!

image

關於我們

快狗叫車前端團隊專注前端技術分享,定期推送高質量文章,歡迎關注點贊。

相關文章