漸進式web應用開發---service worker (二)

閱讀目錄javascript

1. 建立第一個service worker 及環境搭建css

在上一篇文章,咱們已經講解了 service worker 的基本原理,請看上一篇文章 . 從這篇文章開始咱們來學習下 service worker的基本知識。html

在講解以前,咱們先來搭建一個簡單的項目,和以前同樣,首先咱們來看下咱們整個項目目錄結構,以下所示:java

|----- 項目
|  |--- public
|  | |--- js               # 存放全部的js
|  | | |--- main.js        # js入口文件
|  | |--- style            # 存放全部的css
|  | | |--- main.styl      # css 入口文件
|  | |--- index.html       # index.html 頁面
|  | |--- images
|  |--- package.json
|  |--- webpack.config.js
|  |--- node_modules

如上目錄結構就是咱們項目的最簡單的目錄結構。咱們先來看下咱們各個目錄的文件代碼。node

index.html 代碼以下:webpack

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>service worker 實列</title>
</head>
<body>
  <div id="app">22222</div>
</body>
</html>

其餘的文件代碼目前能夠忽略不計,基本上沒有什麼代碼。所以咱們在項目的根目錄下 運行  npm run dev 後,就會啓動咱們的頁面。以下運行的頁面。以下所示:git

如上就是咱們的頁面簡單的整個項目的目錄架構了,如今咱們來建立咱們的第一個 service worker了。github

一:建立咱們的第一個service workerweb

首先從當前頁面註冊一個新的service worker, 所以在咱們的 public/js/main.js 添加以下代碼:chrome

// 加載css樣式
require('../styles/main.styl');

console.log(navigator);

if ("serviceWorker" in navigator) {
  navigator.serviceWorker.register('/sw.js')
    .then(function(registration){
      console.log("Service Worker registered with scope: ", registration.scope);
    }).catch(function(err) {
      console.log("Service Worker registered failed:", err);
    });
}

如上代碼,首先在咱們的chrome下打印 console.log(navigator); 看到以下信息:

切記:要想支持service worker 的話,在本地必須以 http://localhost:8081/ 或 http://127.0.0.1:8081 來訪問頁面,在線上必須以https來訪問頁面,不然的話,瀏覽器是不支持的,由於http訪問會涉及到service worker安全性問題。 好比我在本地啓動的是以 http://0.0.0.0:8081/ 這樣來訪問頁面的話,瀏覽器是不支持 service worker的。以下所示:

如上代碼,使用了if語句判斷了瀏覽器是否支持 service worker, 若是支持的話,咱們會使用 navigator.serviceWorker.register 方法註冊了咱們的 service worker,該方法接收2個參數,第一個參數是咱們的腳本的URL,第二個參數是做用域(晚點會講到)。

如上register方法會返回一個promise對象,若是promise成功,說明註冊service worker 成功,就會執行咱們的then函數代碼,不然的話就會執行咱們的catch內部代碼。

如上代碼咱們的 navigator.serviceWorker.register('/sw.js'), 註冊了一個 sw.js,所以咱們須要在咱們的 項目的根目錄下新建一個 sw.js 。目前它沒有該目錄文件,而後咱們繼續刷新頁面,能夠看到它進入了catch語句代碼;以下所示:

如今咱們須要在 咱們的項目根目錄下新建 sw.js.

注意:咱們的sw.js文件路徑放到了項目的根目錄下,這就意味着 serviceworker 和網站是同源的,所以在 項目的根目錄下的全部請求均可以代理的。若是咱們把 sw.js 放入到咱們的 public 目錄下的話,那麼就意味這咱們只能代理public下的網絡請求了。可是咱們能夠在註冊service worker的時候傳入一個scope選項,用來覆蓋默認的service worker的做用域。好比以下:

navigator.serviceWorker.register('/sw.js');
navigator.serviceWorker.register('/sw.js', {scope: '/'});

如上兩條命令是徹底相同的做用域了。

下面咱們的兩條命令將註冊兩個不一樣的做用域,由於他們是放在兩個不一樣的目錄下。以下代碼:

navigator.serviceWorker.register('/sw.js', {scope: '/xxx'});
navigator.serviceWorker.register('/sw22.js', {scope: '/yyy'});

如今在咱們的項目根目錄下有sw.js 這個文件,所以這個時候咱們再刷新下咱們的頁面,就能夠看到打印出消息出來了 Service Worker registered with scope:  http://localhost:8081/ ,說明註冊成功了。以下所示:

下面咱們繼續在咱們的 項目的根目錄 sw.js 添加代碼以下:

console.log(self);

self.addEventListener("fetch", function(e) {
  console.log("Fetch request for: ", e.request.url);
});

如上代碼,咱們首先能夠打印下self是什麼,在service worker中,self它是指向service worker自己的。咱們首先在控制檯中看看self究竟是什麼,咱們能夠先打印出來看看,以下所示:

如上代碼,咱們的service worker添加了一個事件監聽器,這個監聽器會監聽全部通過service worker的fetch事件,並容許咱們的回調函數,咱們修改sw.js後,咱們並無看到控制檯打印咱們的消息,所以咱們先來簡單的學習下咱們的service worker的生命週期。

當咱們修改sw.js 代碼後,這些修改並無在刷新瀏覽器以後當即生效,這是由於原先的service worker依然處於激活狀態,可是咱們新註冊的 service worker 仍然處於等待狀態,若是這個時候咱們把原先第一個service worker中止掉就能夠了,爲何有這麼多service worker,那是由於我刷新下頁面,瀏覽器會從新執行咱們的main.js 代碼中註冊 sw.js 代碼,所以會有不少service worker,如今咱們要怎麼作呢?咱們打開咱們的chrome控制檯,切換到 Application 選項,而後禁用掉咱們的第一個正處於激活狀態下的service worker 便可,以下圖所示:

禁用完成後,咱們再刷新下頁面便可看到咱們console.log(self);會打印出來了,說明已經生效了。

可是咱們如今是否注意到,咱們的監聽fetch事件,第一次刷新的時候沒有打印出 console.log("Fetch request for: ", e.request.url); 這個信息,這是由於service worker第一次是註冊,註冊完成後,咱們才能夠監聽該事件,所以當咱們第二次之後刷新的時候,咱們就可使用fetch事件監聽到咱們頁面上全部的請求了,咱們能夠繼續第二次刷新後,在控制檯咱們能夠看到以下信息,以下圖所示:

下面爲了使咱們更能理解咱們的整個目錄架構,咱們再來看下咱們整個目錄結構變成以下樣子:

|----- 項目
|  |--- public
|  | |--- js               # 存放全部的js
|  | | |--- main.js        # js入口文件
|  | |--- style            # 存放全部的css
|  | | |--- main.styl      # css 入口文件
|  | |--- index.html       # index.html 頁面
|  | |--- images
|  |--- package.json
|  |--- webpack.config.js
|  |--- node_modules
|  |--- sw.js

2. 使用service worker 對請求攔截

 咱們第二次之後刷新的時候,咱們能夠監聽到咱們頁面上全部的請求,那是否是也意味着咱們能夠對這些請求進行攔截,而且修改代碼,而後返回呢?固然能夠的,所以咱們把sw.js 代碼改爲以下這個樣子:代碼以下所示:

self.addEventListener("fetch", function(e) {
  if (e.request.url.includes("main.css")) {
    e.respondWith(
      new Response(
        "#app {color:red;}",
        {headers: { "Content-Type": "text/css" }}
      )
    )
  }
});

如上代碼,咱們監聽fetch事件,並檢查每一個請求的URL是否包含咱們的 main.css 字符串,若是包含的話,service worker 會動態的建立一個Response對象,在響應中包含了自定義的css,並使用這個對象做爲咱們的響應,而不會向遠程服務器請求這個文件。效果以下所示:

3. 從web獲取內容

在第二點中,咱們攔截main.css ,咱們經過指定內容和頭部,從零開始建立了一個新的響應對象,並使用它做爲響應內容。可是service worker 更普遍的用途是響應來源於網絡請求。咱們也能夠監聽圖片。

咱們在咱們的index.html頁面中添加一張圖片的代碼;以下所示:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>service worker 實列</title>
</head>
<body>
  <div id="app">22222</div>
  <img src="/public/images/xxx.jpg" />
</body>
</html>

而後咱們在頁面上瀏覽下,效果以下:

如今咱們經過service worker來監聽該圖片的請求,而後替換成另一張圖片,sw.js 代碼以下所示:

self.addEventListener("fetch", function(e) {
  if (e.request.url.includes("/public/images/xxx.jpg")) {
    e.respondWith(
      fetch("/public/images/yyy.png")
    );
  }
});

而後咱們繼續刷新下頁面,把以前的service worker禁用掉,繼續刷新頁面,咱們就能夠看到以下效果:

和咱們以前同樣,咱們監聽fetch事件,此次咱們查找的字符串是 "/public/images/xxx.jpg",當檢測到有這樣的請求的時候,咱們會使用fetch命令建立一個新的請求,並把第二張圖片做爲響應返回,fetch它會返回一個promise對象。

注意:fetch方法的第一個參數是必須傳遞的,能夠是request對象,也能夠是包含一個相對路徑或絕對路徑的URL的字符串。好比以下:

// url 請求
fetch("/public/images/xxx.jpg");

// 經過request對象中的url請求
fetch(e.request.url);

// 經過傳遞request對象請求,在這個request對象中,除了url,可能還包含額外的頭部信息,表單數據等。
fetch(e.request);

fetch它還有第二個參數,能夠包含是一個對象,對象裏面是請求的選項。好比以下:

fetch("/public/images/xxx.jpg", {
  method: 'POST',
  credentials: "include"
});

如上代碼,對一個圖片發起了一個post請求,並在頭部中包含了cookie信息。fetch會返回一個promise對象。

4. 捕獲離線請求

咱們如今有了上面的service worker的基礎知識後,咱們來使用service worker檢測用戶什麼時候處於離線狀態,若是檢測到用戶是處於離線狀態,咱們會返回一個友好的錯誤提示,用來代替默認的錯誤提示。所以咱們須要把咱們的sw.js 代碼改爲以下所示:

self.addEventListener("fetch", function(event) {
  event.respondWith(
    fetch(event.request).catch(function() {
      return new Response(
        "歡迎來到咱們的service worker應用"
      )
    })
  );
});

如上代碼咱們監聽並捕獲了全部的fetch事件,而後使用另外一個徹底相同的fetch操做進行相應,咱們的fetch它是返回的是一個promise對象,所以它有catch失敗時候來捕獲異常的,所以當咱們刷新頁面後,而後再切換到離線狀態時候,咱們能夠看到頁面變成以下提示了,以下圖所示:

5. 建立html響應

 如上響應都是對字符串進行響應的,可是咱們也能夠拼接html返回進行響應,好比咱們能夠把sw.js 代碼改爲以下:

var responseContent = `<html>
  <body>
    <head>
      <meta charset="UTF-8">
      <title>service+worker異常</title>
      <style>body{color:red;font-size:18px;}</style>
    </head>
    <h2>service worker異常的處理</h2>
  </body>
`

self.addEventListener("fetch", function(event) {
  console.log(event.request);
  event.respondWith(
    fetch(event.request).catch(function() {
      return new Response(
        responseContent,
        {headers: {"Content-Type": "text/html"}}
      )
    })
  );
});

而後咱們繼續刷新頁面,結束掉第一個service worker應用,能夠看到以下所示:

如上代碼,咱們先定義了給離線用戶的html內容,並將其賦值給 responseContent變量中,而後咱們添加一個事件監聽器,監聽全部的fetch事件,咱們的回調函數就會被調用,它接收一個 event事件對象做爲參數,隨後咱們調用了該事件對象的 respondWith 方法來響應這個事件,避免其觸發默認行爲。

respondWith方法接收一個參數,它能夠是一個響應對象,也能夠是一段經過promise得出響應對象的代碼。

如上咱們調用fetch,並傳入原始請求對象,不只僅是URL,它還包括頭部信息、cookie、和請求方法等,如上咱們的打印的 console.log(event.request); 以下所示:

fetch方法它返回的是一個promise對象,若是用戶和服務器在線正常的話,那麼他們就返回頁面中正常的頁面,那麼這個時候promise對象會返回完成狀態。以下咱們把利息勾選框去掉,再刷新下頁面,能夠看到以下信息:

如上頁面正常返回了,它就不會進入promise中catch異常的狀況下,可是若是離線狀態或者異常的狀況下,那麼就會進入catch異常代碼。
所以catch函數就會被調用到。

6. 理解 CacheStorage緩存

在前面的學習當中,當用戶離線的時候,咱們能夠向他們的頁面顯示自定義的html內容,而不是瀏覽器的默認錯誤提示,可是這樣也不是最好的處理方式,咱們如今想要作的是,若是用戶在線的時候,咱們以正常的index.html內容顯示給用戶,包括html內容,圖片和css樣式等,當用戶離線的時候,咱們的目標仍是想顯示index.html中的內容,圖片和css樣式等這樣的顯示給用戶,也就是說,不論是在線也好,離線也好,咱們都喜歡這樣顯示內容給用戶訪問。所以若是咱們想要實現這麼一個功能的話,咱們須要在用戶在線訪問的時候,使用緩存去拿到文件,而後當用戶離線的時候,咱們就顯示緩存中的文件和內容便可實現這樣的功能。

什麼是CacheStorage?

CacheStorage 是一種全新的緩存層,它擁有徹底的控制權。既然CacheStorage能夠緩存,那麼咱們如今要想的問題是,咱們何時進行緩存呢?到目前爲止,咱們先來看下 service worker的簡單的生命週期以下:

安裝中 ----> 激活中 -----> 已激活

咱們以前是使用 service worker 監聽了fetch事件,那麼該事件只可以被激活狀態的service worker所捕獲,可是咱們目前不能在該事件中來緩存咱們的文件。咱們須要監聽一個更早的事件來緩存咱們的service worker頁面所依賴的文件。

所以咱們須要使用 service worker中的install事件,在每一個service worker中,該事件只會發生一次。即首次在註冊以後以及激活以前,該事件會發生。在service worker接管頁面並開始監聽fetch事件以前,咱們經過該事件進行監聽,所以就能夠很好的緩存咱們全部離線可用的文件。

若是安裝有問題的話,咱們能夠在install事件中取消安裝service worker,若是在緩存時出現問題的話,咱們能夠終止安裝,由於當用戶刷新頁面後,瀏覽器會在用戶下次訪問頁面時再次嘗試安裝service worker。經過這種方式,咱們能夠有效的爲service worker建立安裝依賴,也就是說在service worker安裝並激活以前,咱們必須下載並緩存這些文件。

所以咱們如今把 sw.js 所有代碼改爲以下代碼:

// 監聽install的事件
self.addEventListener("install", function(e) {
  e.waitUntil(
    caches.open("cacheName").then(function(cache) {
      return cache.add("/public/index.html");
    })
  )
});

如上代碼,咱們爲install事件添加了事件監聽器,在新的service worker註冊以後,該事件會當即在其安裝階段被調用。

如上咱們的service worker 依賴於 "/public/index.html", 咱們須要驗證它是否成功緩存,而後咱們才能認爲它安裝成功,並激活新的 service worker,由於須要異步獲取文件並緩存起來,因此咱們須要延遲install事件,直到異步事件完成,所以咱們這邊使用了waitUntil,waitUntil會延長事件存在的時間,直到promise成功,咱們纔會調用then方法後面的函數。
在waitUntil函數中,咱們調用了 caches.open並傳入了緩存的名稱爲 "cacheName". caches.open 打開並返回一個現有的緩存,若是沒有找到該緩存,咱們就建立該緩存並返回他。最後咱們執行then裏面的回調函數,咱們使用了 cache.add("/public/index.html").這個方法將請求文件放入緩存中,緩存的鍵名是 "/public/index.html"。

2. 從CacheStorage中取回請求

上面咱們使用 cache.add 將頁面的離線版本存儲到 CacheStorage當中,如今咱們須要作的事情是從緩存中取回並返回給用戶。

所以咱們須要在sw.js 中添加fetch事件代碼,添加以下代碼:

// 監聽fetch事件
self.addEventListener("fetch", function(e) {
  e.respondWith(
    fetch(e.request).catch(function() {
      return caches.match("/public/index.html");
    })
  )
});

如上代碼和咱們以前的fetch事件代碼很類似,咱們這邊使用 caches.match 從CacheStorage中返回內容。

注意:match方法能夠在caches對象上調用,這樣會在全部緩存中尋找,也能夠在某個特定的cache對象上調用。以下所示:

// 在全部緩存中尋找匹配的請求
caches.match('/public/index.html');

// 在特定的緩存中尋找匹配的請求
cache.open("cacheName").then(function(cache) {
  return cache.match("/public/index.html");
});

match方法會返回一個promise對象,而且向resolve方法傳入在緩存中找到的第一個response對象,當找不到任何內容的時候,它的值是undefined。也就是說,即便match找不到對應的響應的時候,match方法也不會被拒絕。以下所示:

caches.match("/public/index.html").then(function(response) {
  if (response) {
    return response;
  }
});

3. 在demo中使用緩存

在如上咱們已經把sw.js 代碼改爲以下了:

// 監聽install的事件
self.addEventListener("install", function(e) {
  e.waitUntil(
    caches.open("cacheName").then(function(cache) {
      return cache.add("/public/index.html");
    })
  )
});

// 監聽fetch事件
self.addEventListener("fetch", function(e) {
  e.respondWith(
    fetch(e.request).catch(function() {
      return caches.match("/public/index.html");
    })
  )
});

當咱們第一次訪問頁面的時候,咱們會監聽install事件,對咱們的 "/public/index.html" 進行緩存,而後當咱們切換到離線狀態的時候,咱們再次刷新能夠看到咱們的頁面只是緩存了 index.html頁面,可是頁面中的css和圖片並無緩存,以下所示:

如今咱們要作的事情是,咱們須要對咱們全部頁面的上的css,圖片,js等資源文件進行緩存,所以咱們的sw.js 代碼須要改爲以下所示:

var CACHE_NAME = "cacheName";
var CACHE_URLS = [
  "/public/index.html",
  "/main.css",
  "/public/images/xxx.jpg"
];

self.addEventListener("install", function(e) {
  e.waitUntil(
    caches.open(CACHE_NAME).then(function(cache) {
      return cache.addAll(CACHE_URLS);
    })
  )
});

// 監聽fetch事件
self.addEventListener("fetch", function(e) {
  e.respondWith(
    fetch(e.request).catch(function() {
      return caches.match(e.request).then(function(response) {
        if (response) {
          return response;
        } else if (e.request.headers.get("accept").includes("text/html")) {
          return caches.match("/public/index.html");
        }
      })
    })
  )
});

如上代碼,咱們設置了兩個變量,第一個變量 CACHE_NAME 是緩存的名稱,第二個變量是一個數組,它包含了一份須要存儲的URL列表。

而後咱們使用了 cache.addAll()方法,它接收的參數是一個數組,它的做用是把數組裏面的每一項存入緩存當中去,固然若是任何一個緩存失敗的話,那麼它返回的promise會被拒絕。

咱們監聽了install事件後對全部的資源文件進行了緩存後,當用戶處於離線狀態的時候,咱們使用fetch的事件來監聽全部的請求。當請求失敗的時候,咱們會調用catch裏面的函數來匹配全部的請求,它也是返回一個promise對象,當匹配成功後,找到對應的項的時候,直接從緩存裏面讀取,若是沒有找到的話,就直接返回 "/public/index.html" 的內容做爲代替。爲了安全起見,咱們在返回"/public/index.html" 以前,咱們還進行了一項檢查,該檢查確保請求是包含 text/html的accept的頭部,所以咱們就不會返回html內容給其餘的請求類型。好比圖片,樣式請求等。

咱們以前建立一個html響應的時候,必須將其 Content-Type的值定義爲 text/html,方便瀏覽器能夠正確的響應識別它是html類型的,那麼爲何咱們這邊沒有定義呢,而直接返回了呢?那是由於咱們的 cache.addAll()請求並緩存的是一個完整的response對象,該對象不只包含了響應體,還包含了服務器返回的任何響應頭。

注意:使用 caches.match(e.request) 來查找緩存中的條目會存在一個陷阱。

好比用戶可能不會老是以相同的url來訪問咱們的頁面,好比它會從其餘的網站中跳轉到咱們的頁面上來,好比後面帶了一些參數,好比:"/public/index.html?id=xxx" 這樣的,也就是說url後面帶了一些查詢字符串等這些字段,若是咱們仍是和以前同樣進行匹配,是匹配不到的,所以咱們須要在match方法中添加一個對象選項;以下所示:

caches.match(e.request, {ignoreSearch: true});

這樣的,經過 ignoreSearch 這個參數,通知match方法來忽略查詢字符串。

如今咱們先請求下咱們的頁面,而後咱們勾選離線複選框,再來查看下咱們的頁面效果以下所示:

如上能夠看到,咱們在離線的狀態下,頁面顯示也是正常的。

7. 理解service worker生命週期

 service worker 的生命週期以下圖所示:

installing(正在安裝)

當咱們使用 navigator.serviceWorker.register 註冊一個新的 service worker的時候,javascript代碼就會被下載、解析、並進入安裝狀態。若是安裝成功的話,service worker 就會進入 installed(已安裝)的狀態。可是若是在安裝過程當中出現錯誤,腳本將被永久進入 redundant(廢棄中)。

installed/waiting(已安裝/等待中)

一旦service worker 安裝成功了,就會進入 installed狀態,通常狀況中,會立刻進入 activating(激活中)狀態。除非另外一個正在激活的 service worker 依然在被控制中。在這種狀況下它會維持在 waiting(等待中)狀態。

activating(激活中)

在service worker激活並接管應用以前,會觸發 activate 事件。

activated(已激活)

一旦 service worker 被激活了,它就準備好接管頁面並監聽功能性事件(好比fetch事件)。

redundant(廢棄)

若是service worker在註冊或安裝過程當中失敗了,或者被新的版本代替,就會被置爲 redundant 狀態,處於這種 service worker將再也不對應用產生任何影響。

注意:service worker的狀態和瀏覽器的任何一個窗口或標籤頁都沒有關係的,也就是說 若是service worker是activated(已激活)狀態的話,它就會保持這個狀態。

8. 理解 service worker 註冊過程

 當咱們第一次訪問咱們的網站的時候(咱們也能夠經過刪除service worker後刷新頁面的方式進行模擬),頁面就會加載咱們的main.js 代碼,以下所示:

if ("serviceWorker" in navigator) {
  navigator.serviceWorker.register('/sw.js', {scope: '/'}).then(function(registration) {
    console.log("Service Worker registered with scope: ", registration.scope);
  }).catch(function(err) {
    console.log("Service Worker registered failed:", err);
  });
}

那麼咱們的應用就會註冊service worker,那麼service worker的文件將會被下載,而後就開始安裝,install事件就會被觸發,而且在service worker整個生命週期中只會觸發一次,而後觸發了咱們的函數,將調用的時機記錄在控制檯中。service worker隨後就會進入 installed狀態。而後當即就會變成 activating 狀態。這個時候,咱們的另外一個函數就會被觸發,所以 activate事件就會把狀態記錄到控制檯中。最後,service worker 進入 activated(已激活)狀態。如今service worker被激活狀態了。

可是當咱們的service worker 正在安裝的時候,咱們的頁面已經開始加載而且渲染了。也就是說 service worker 變成了 active狀態了。所以就不能控制頁面了,只有咱們再刷新頁面的時候,咱們的已經被激活的service worker才能控制頁面。所以咱們就能夠監聽和操控fetch事件了。

9. 理解更新service worker

 咱們首先來修改下 sw.js 代碼,改爲以下:

self.addEventListener("fetch", function(e) {
  if (e.request.url.includes("main.css")) {
    e.respondWith(
      new Response(
        "#app {color:red;}",
        {headers: { "Content-Type": "text/css" }}
      )
    )
  }
});

而後咱們再刷新頁面,發現頁面中的顏色並無改變,爲何呢?可是咱們service worker明明控制了頁面,可是頁面爲何沒有生效?
咱們能夠打開chrome開發者工具中 Application -> Service Worker 來理解這段代碼的含義:以下圖所示:

如上圖所示,頁面註冊了多個service worker,可是隻有一個在控制頁面,舊的service worker是激活的,而新的service worker仍處於等待狀態。

每當頁面加載一個激活的service worker,就會檢查 service worker 腳本的更新。若是文件在當前的service worker註冊以後發生了修改,新的文件就會被註冊和安裝。安裝完成後,它並不會替換原先的service worker,而是會保持 waiting 狀態。它將會一直保持等待狀態,直到原先的service worker做用域中的每一個標籤和窗口關閉。或者導航到一個再也不控制範圍內的頁面。可是咱們能夠關閉原先的service worker, 那麼原先的service worker 就會變成廢棄狀態。而後咱們新的service worker就會被激活。所以咱們能夠以下圖所示:

可是若是咱們想修改完成後,不結束原來的service worker的話,想改動代碼,刷新一下就生效的話,咱們須要把 Update on reload這個複選框勾上便可生效,好比以下所示:

注意:

那麼爲何安裝完成新的service worker 不能實時生效呢?好比說安裝新的service worker不能控制新的頁面呢?原先的service worker 控制原先的頁面呢?爲何瀏覽器不能跟蹤多個service worker 呢?爲何全部的頁面都必須由單一的service worker所控制呢?

咱們能夠設想下以下這麼一個場景,若是咱們發佈了一個新版本的service worker,而且該service worker的install事件會從緩存中刪除 update.json 該文件,並添加 update2.json文件做爲代替,而且修改fetch事件,讓其在請求用戶數據的時候,返回新的文件,若是多個service worker控制了不一樣的頁面,那麼舊的service worker控制的頁面可能會在緩存中搜索舊的 update.json 文件,可是該文件又被刪除了,那麼就會致使該應用奔潰。所以咱們須要被確保打開全部的標籤頁或窗口由一個service worker控制的話,就能夠避免相似的問題發生。

10. 理解緩存管理和清除緩存

爲何須要管理緩存呢?

咱們首先把咱們的sw.js 代碼改爲原先的以下代碼:

var CACHE_NAME = "cacheName";
var CACHE_URLS = [
  "/public/index.html",
  "/main.css",
  "/public/images/xxx.jpg"
];

self.addEventListener("install", function(e) {
  e.waitUntil(
    caches.open(CACHE_NAME).then(function(cache) {
      return cache.addAll(CACHE_URLS);
    })
  )
});

// 監聽fetch事件
self.addEventListener("fetch", function(e) {
  e.respondWith(
    fetch(e.request).catch(function() {
      return caches.match(e.request).then(function(response) {
        if (response) {
          return response;
        } else if (e.request.headers.get("accept").includes("text/html")) {
          return caches.match("/public/index.html");
        }
      })
    })
  )
});

如上代碼,咱們的service worker會在安裝階段下載並緩存所須要的文件。若是但願它再次下載並緩存這些文件的話,咱們就須要觸發另外一個安裝事件。在sw.js中的,咱們把 CACHE_NAME 名字改下便可。好比叫 var CACHE_NAME = "cacheName2"; 這樣的。

經過如上給緩存名稱添加版本號,而且每次修改文件時自增它,能夠實現兩個目的。

1)修改緩存名稱後,瀏覽器就知道安裝新的service worker來代替舊的service worker了,所以會觸發install事件,所以會致使文件被下載並存儲在緩存中。

2)它爲每個版本的service worker都建立了一份單獨的緩存。即便咱們更新了緩存,在用戶關閉全部頁面以前,舊的service worker依然是激活的。舊的service worker可能會用到緩存中的某些文件,而這些文件又是能夠被新的service worker所修改的,經過讓每一個版本的service worker所擁有本身的緩存,就能夠確保不會出現其餘的異常狀況。

如何清除緩存呢?

caches.delete(cacheName); 該方法接收一個緩存名字做爲第一個參數,並刪除對應的緩存。

caches.keys(); 該方法是獲取全部緩存的名稱,而且返回一個promsie對象,其完成的時候會返回一個包含緩存名稱的數組。以下所示:

caches.keys().then(function(cacheNames){
  cacheNames.forEach(function(cacheName){
    caches.delete(cacheName);
  });
});

如何緩存管理呢?

在service worker生命週期中,咱們須要實現以下目標:
1)每次安裝 service worker,咱們都須要建立一份新的緩存。
2)當新的service worker激活的時候,就能夠安全刪除過去的service worker 建立的全部緩存。

所以咱們在現有的代碼中,添加一個新的事件監聽器,監聽 activate 事件。

self.addEventListener("activate", function(e) {
  e.waitUntil(
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        cacheNames.map(function(cacheName) {
          if (CACHE_NAME !== cacheName && cacheName.startWith("cacheName")) {
            return caches.delete(cacheName);
          }
        })
      )
    })
  )
});

11. 理解重用已緩存的響應

如上咱們的帶版本號緩存已經實現了,爲咱們提供了一個很是靈活的方式來控制咱們的緩存,而且保持最新的緩存。可是緩存內的實現是很是低下的。

好比每次咱們建立一個新的緩存的時候,咱們會使用 cache.add() 或 cache.addAll() 這樣的方法來緩存應用須要的全部文件。可是,若是用戶已經在本地擁有了 cacheName 這個緩存的話,那若是這個時候咱們建立 cacheName2 這個緩存的話,咱們發現咱們建立的 cacheName2 須要緩存的文件 在 cacheName 已經有了,而且咱們發現這些文件是永遠不會被改變的。若是咱們從新緩存這些文件的話,就浪費了寶貴的帶寬和時間從網絡再次下載他們。

爲了解決如上的問題,咱們須要以下作:

若是咱們建立一個新的緩存,咱們首先要遍歷一份不可變的文件列表,而後從現有的緩存中尋找他們,並直接複製到新的緩存中。所以咱們的sw.js 代碼變成以下所示:

var CACHE_NAME = "cacheName";
var immutableRequests = [
  "/main.css",
  "/public/images/xxx.jpg"
];

var mutableRequests = [
  "/public/index.html"
];

self.addEventListener("install", function(e) {
  e.waitUntil(
    caches.open(CACHE_NAME).then(function(cache) {
      var newImmutableRequests = [];
      return Promise.all(
        immutableRequests.map(function(url) {
          return caches.match(url).then(function(response) {
            if (response) {
              return cache.put(url, response);
            } else {
              newImmutableRequests.push(url);
              return Promise.resolve();
            }
          });
        })
      ).then(function(){
        return cache.addAll(newImmutableRequests.concat(mutableRequests));
      })
    })
  )
});

如上代碼。

1)immutableRequests 數組中包含了咱們知道永遠不會改變的資源URL,這些資源能夠安全地在緩存之間複製。

2)mutableRequests 中包含了每次建立新緩存時,咱們都須要從網絡中請求的url。

如上代碼,咱們的 install 事件會遍歷全部的 immutableRequests,而且在全部現有的緩存中尋找他們。若是被找到的話,都會使用cache.put()複製到新的緩存中。若是沒有找到該資源的話,會被放入到新的 newImmutableRequests 數組中。

一旦全部的請求被檢查完畢,代碼就會使用 cache.addAll()來緩存 mutableRequests 和 newImmutableRequests 中全部的URL。

github源碼查看

相關文章
相關標籤/搜索