天天一點網站優化之:前端靜態資源緩存 sevice worker


title: 天天一點網站優化之:前端靜態資源緩存 sevice worker date: 2019-06-28 17:00:00 tags: -JavaScript categories: JavaScript

在前一課咱們講過,經過在服務端設置http請求頭字段的方式,控制瀏覽器的靜態資源緩存規則 那麼,做爲前端開發,有沒有不須要後端配合的緩存方式呢? 下面,咱們就一塊兒來了解一下,在客戶端代碼控制web離線緩存的sevice worker。javascript

Service Worker 是 Chrome 團隊提出和力推的一個 WEB API,用於給 web 應用提供高級的可持續的後臺處理能力。 該 WEB API 標準起草於 2013 年,於 2014 年歸入 W3C WEB 標準草案,當前還在草案階段。css

Service workers 本質上充當Web應用程序與瀏覽器之間的代理服務器,也能夠在網絡可用時做爲瀏覽器和網絡間的代理。 它們可以建立有效的離線體驗,攔截網絡請求並基於網絡是否可用以及更新的資源是否駐留在服務器上來採起適當的動做。 他們還容許訪問推送通知和後臺同步API。前端

當瀏覽器發送請求時,首先到達sw腳本中,若是沒有命中,再轉發給http請求。java

sevice worker的特色

  • 網站必須使用 HTTPS。除了使用本地開發環境調試時(如域名使用 localhost)
  • 運行於瀏覽器後臺,能夠控制打開的做用域範圍下全部的頁面請求
  • 單獨的做用域範圍,單獨的運行環境和執行線程
  • 不能操做頁面 DOM。但能夠經過事件機制來處理

sevice worker瀏覽器支持狀況 web

image

sevice worker的生命週期

註冊 -> 安裝 -> 激活 -> 廢棄chrome

註冊register

  • service worker URL 經過 serviceWorkerContainer.register() 來獲取和註冊。
  • 若是註冊成功,service worker 就在 ServiceWorkerGlobalScope 環境中運行; 這是一個特殊類型的 woker 上下文運行環境,與主運行線程(執行腳本)相獨立,同時也沒有訪問 DOM 的能力。
  • service worker 如今能夠處理事件了
  • chrome 瀏覽器下,註冊成功後,能夠打開 chrome://serviceworker-internals/ 查看瀏覽器的 Service Worker 信息

也能夠在開發者工具中查看瀏覽器中sevice worker的狀況 後端

image
註冊sevice worker:

f ('serviceWorker' in navigator) {
	    navigator.serviceWorker
	        .register('sw.js', {scope: '/'})
	        .then(registration => {
	        	console.log('ServiceWorker 註冊成功!做用域爲: ', registration.scope) 
	    	})
	        .catch(err => {
	        	console.log('ServiceWorker 註冊失敗: ', err)
	    	});
	}
複製代碼

代碼解析:promise

  • sw.js是sevice worker的腳本,sevice worker的全部行爲邏輯都在其中呈現
  • sw.js所在的位置決定了sevice worker的做用域,做用域內全部的頁面請求都會被 sevice worker所監控, scope參數能夠修改其做用域。

安裝(install)和更新

初次打開受 service worker 控制的頁面後,會觸發install事件

  • 當 oninstall 事件的處理程序執行完畢後,能夠認爲 service worker 安裝完成了

sw.js文件有更新,也會觸發install事件

  • 若是 sw.js 文件的內容有改動,當訪問網站頁面時瀏覽器獲取了新的文件,它會認爲有更新,因而會安裝新的文件並觸發 install 事件。可是此時已經處於激活狀態的舊的 Service Worker 還在運行,新的 Service Worker 完成安裝後會進入 waiting 狀態。 直到全部已打開的頁面都關閉,舊的 Service Worker 自動中止,新的 Service Worker 纔會在接下來打開的頁面裏生效
    image
  • 能夠在 install 事件中執行 skipWaiting 方法跳過 waiting 狀態,而後會直接進入 activate 階段。接着在 activate 事件發生時,經過執行 clients.claim 方法,更新全部客戶端上的 Service Worker。 示例代碼:
// 安裝階段跳過等待,直接進入 active
self.addEventListener('install', function(event) {
    event.waitUntil(self.skipWaiting());
});
複製代碼

激活active

  • 當 service worker 安裝完成後,會接收到一個激活事件(activate event)。 onactivate 主要用途是清理先前版本的service worker 腳本中使用的資源
  • 經過監聽 activate 事件你能夠作一些預處理,如對於舊版本的更新、對於無用緩存的清理等

在下面的示例中,咱們實現對舊版本的緩存資源清理瀏覽器

this.addEventListener('activate', event => {
    const cacheWhitelist = ['lzwme_cache_v1.6.0'];

    event.waitUntil(
        // 遍歷當前的緩存,刪除 指定版本號 以外的全部緩存
        caches.keys().then(keyList => {
            return Promise.all(keyList.map(key => {
                if (cacheWhitelist.indexOf(key) === -1) {
                    return caches.delete(key);
                }
            }));
        })
    );
});
複製代碼

傳給 waitUntil() 的 Promise 會阻塞其餘的事件,直到它完成。這能夠確保清理操做會在第一次 fetch 事件以前完成緩存

fetch事件與緩存策略

  • 當瀏覽器發起請求時,會觸發 fetch 事件, fetch事件會攔截全部做用域內的請求
  • 攔截請求後,根據request url和緩存作比對,若是當請求資源已經在緩存裏了,直接返回緩存裏的內容;不然使用 fetch API 繼續請求,而且將請求成功後的response存入緩存中

參考下面的示例:

self.addEventListener('fetch', function(event) {
  // console.log('Handling fetch event for', event.request.url);

  event.respondWith(
    
    // Opens Cache objects that start with 'font'.
    caches.open(CURRENT_CACHES['carry']).then(function(cache) {
      return cache.match(event.request).then(function(response) {
        // 若是命中了緩存,直接返回緩存中的數據
        if (response) {
          console.log(' Found response in cache:', response);
          return response;
        }

        // 請求是stream,只能使用一次
        var fetchRequest = event.request.clone();

        return fetch(fetchRequest)
            .then(function(response) {
                //請求不成功,則不存入緩存
                if(!response || response.status !== 200) {
                    return response;
                }
                // 若是沒有命中緩存,將請求和響應緩存到cache中
                // 響應也是stream,只能使用一次,一次用於緩存,一次用於瀏覽器響應
                var responseToCache = response.clone();
                caches.open(CURRENT_CACHES['carry'])
                    .then(function(cache) {
                        // 抓取請求及其響應,並將其添加到給定的cache
                        cache.put(event.request, responseToCache);
                    });
                return response;
            }); 

      }).catch(function(error) {
        
        // Handles exceptions that arise from match() or fetch().
        console.error(' Error in fetch handler:', error);

        throw error;
      });
    })
  );
});
複製代碼

緩存策略優化

  • 指定緩存的接口類型,如只緩存js和css請求,其餘類型依然發送http請求

清除舊緩存

  • 給緩存增長版本號,當修改版本號以後,在active階段,全部上一版本的緩存都會被清空

如下完整代碼

var CACHE_VERSION = 2;

// Shorthand identifier mapped to specific versioned cache.
var CURRENT_CACHES = {
  carry: 'version' + CACHE_VERSION
};

const cacheList = [
    'css',
    'js'
]

// 安裝階段跳過等待,直接進入 active
self.addEventListener('install', function(event) {
    event.waitUntil(self.skipWaiting());
});

self.addEventListener('activate', function(event) {
  var expectedCacheNames = Object.keys(CURRENT_CACHES).map(function(key) {
    return CURRENT_CACHES[key];
  });

  // Active worker won't be treated as activated until promise resolves successfully.
  event.waitUntil(
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        // 更新客戶端
        clients.claim(),
        // 清理舊版本
        cacheNames.map(function(cacheName) {
          if (expectedCacheNames.indexOf(cacheName) == -1) {
            console.log('Deleting out of date cache:', cacheName);
            
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
});

self.addEventListener('fetch', function(event) {
  console.log('Handling fetch event for', event.request.url);

  let cached = cacheList.find(c => {return event.request.url.indexOf(c) !== -1 });

  event.respondWith(
    if(cached){
        // 打開指定版本的緩存列表
        // 每一個cache對象和請求的request url 匹配
        caches.open(CURRENT_CACHES['carry']).then(function(cache) {
          return cache.match(event.request).then(function(response) {
            // 若是命中了緩存,直接返回緩存中的數據
            if (response) {
              console.log(' Found response in cache:', response);
              return response;
            }

            // 請求是stream,只能使用一次
            var fetchRequest = event.request.clone();

            return fetch(fetchRequest)
                .then(function(response) {
                    if(!response || response.status !== 200) {
                        return response;
                    }
                    // 若是沒有命中緩存,將請求和響應緩存到cache中
                    // 響應也是stream,只能使用一次,一次用於緩存,一次用於瀏覽器響應
                    var responseToCache = response.clone();
                    caches.open(CURRENT_CACHES['carry'])
                        .then(function(cache) {
                            // 抓取請求及其響應,並將其添加到給定的cache
                            cache.put(event.request, responseToCache);
                        });
                    return response;
                }); 

          }).catch(function(error) {
            
            // Handles exceptions that arise from match() or fetch().
            console.error(' Error in fetch handler:', error);

            throw error;
          });
        })
    }else{
        return fetch(fetchRequest)
            .then(response => {
                return response;
            })
    }
  );
});

複製代碼

廢棄Redundant

致使廢棄的緣由

  • 安裝(install)失敗
  • 激活(activating)失敗
  • 新版本的 Service Worker 替換了它併成爲激活狀態
  • 手動在瀏覽器開發者工具中中止sevice worker

總結

sevice worker的做用,遠遠不止請求資源緩存這一條,基於 Service Worker API 的特性,結合 Fetch API、Cache API、Push API、postMessage API 和 Notification API,能夠在基於瀏覽器的 web 應用中實現 如離線緩存、消息推送、靜默更新等 native 應用常見的功能,以給 web 應用提供更好更豐富的使用體驗。

相關文章
相關標籤/搜索