小白带你了解如何使用service-worker

338 阅读2分钟

1. service-worker是什么

初次见到sw, 我们会想到什么呢?

image.png

上面是一些通用的猜测和想法, 下面我们将进行更为官方的定义.

定义: 一个服务器与浏览器之间的中间人角色,如果网站中注册了service worker那么它可以拦截当前网站所有的请求,进行判断(需要编写相应的判断程序),如果需要向服务器发起请求的就转给服务器,如果可以直接使用缓存的就直接返回缓存不再转给服务器。从而大大提高浏览体验。

image.png

如上图所示, C段发出请求, 先被sw捕获, 在sw中判断是否命中缓存, 如有命中缓存, 则返回缓存;
没有则向服务器发请求.(PS: 这里面还涉及到cache-first和net-first策略)

2. service-worker的原理

  1. 首次看到 Service Worker,我想大家可能会跟我一样都有这东西跟 Web Worker 有什么联系之类的疑问,带着这个疑问让我们来梳理下两者的差异。
    
  2. Web Worker 是现代浏览器提供的一个 JavaScript 多线程解决方案,我们可以将一些复杂、耗时的运算交给 Web Worker 执行以达到释放主线程的目的;Service Worker 则是建立在 Web Worker 之上,旨在通过请求代理、本地缓存、后台同步等机制来提供离线处理能力。两者的主要异同点如下:
    

image.png

3. 生命周期

注册 -> 安装 -> 激活

image.png

  • 首先我们看下如何注册, 代码如下:
      
    
      if ('serviceWorker' in navigator) {
        window.addEventListener('load', function() {
          navigator.serviceWorker.register('/sw.js').then((na) => {
            console.info('注册成功');
            na.onupdatefound = function() {
              var e = na.installing;
              e.onstatechange = function() {
                console.log('e.state =', e.state);
                switch (e.state) {
                  case "installed":
                    navigator.serviceWorker.controller ? console.log("New or updated content is available.") : console.log("Content is now available offline!");
                    break;
                  case "redundant":
                    console.error("The installing service worker became redundant.")
                }
              }
            }
          }).catch((err) => {
            console.error('注册失败');
          });
          // 当前缓存的大小
          navigator.webkitTemporaryStorage.queryUsageAndQuota(console.log);
        });
    
        // 接收数据
        navigator.serviceWorker.addEventListener('message', data => {
          console.log('data from service-worker', data);
        });
      }
    
  • 安装
       
    
    var version = 4;
    
    var CACHE_NAME = "index-image-" + version
    
    let urlsToCache = [
      // 'http://localhost:8080/getNews',
      'http://localhost:5173/topic/tmp1.json',
      'http://localhost:5173/src/main.ts',
      'https://pic3.zhimg.com/v2-58d652598269710fa67ec8d1c88d8f03_r.jpg?source=1940ef5c',
    ];
    self.addEventListener('install', function(event) {
      //调试时跳过等待过程
      self.skipWaiting();
    
      event.waitUntil(
        caches.open(CACHE_NAME)
          .then(cache => {
            return cache.addAll(urlsToCache);
          })
      );
    });
    
  • 激活
      
    
    self.addEventListener('activate', function(event) {
      event.waitUntil(
        caches.keys().then(function(cacheNames) {
          return Promise.all(
            cacheNames.map(function(cacheName) {
              // 判断是否有效缓存, 且删除无效缓存
              if (CACHE_NAME !== cacheName) {
                console.log('Deleting out of date cache:', cacheName);
                return caches.delete(cacheName);
              }
            })
          );
        })
      );
    });
    
  • 命中缓存
    
    
    self.addEventListener('fetch', function(event) {
    
      event.respondWith(
        caches.open(CACHE_NAME).then(function(cache) {
          //去缓存cache中找对应的url的值
    
          console.log(urlsToCache, event.request.url);
          if (!urlsToCache.some(url => event.request.url.includes(url))) {
            return fetch(event.request);
          }
          self.clients.matchAll().then(function(clients) {
            clients.forEach(function(client) {
              // sw端向主线程发送消息
              client.postMessage({
                msg: 'hello world' + client.id,
                source: 'service-worker',
              });
            });
          });
    
          return cache.match(event.request.url.split('?')[0]).then(function(response) {
              //如果找到了,就返回value
              console.log(response);
              if (response) {
                  return response;
              }
              throw Error('The cached response that was expected is missing,.');
          });
        }).catch(function(e) {
          // 如果没找到则请求该资源
          console.warn('Couldn\'t serve response for "%s" from cache: %O', event.request.url, e);
          return fetch(event.request);
        })
      );
    });
    
    

5. 与主线程通信-postMessage

  •     主线程接收代码如下: 
    
        // 接收数据
        navigator.serviceWorker.addEventListener('message', data => {
          console.log('data from service-worker', data);
        });
    
  • 主线程向sw发送消息, sw接收: 
    
    // 主线程代码
    // 发送数据
      window.navigator.serviceWorker.controller.postMessage({
        text: '我是来自于主线程的数据'
      });
      
    // service-worker代码
      self.addEventListener('message', function(data) {
        console.log('data from main-js =', data);
      });
    

6. demo地址