serviceworker简单应用

Service Worker介绍:

Service Worker 是一种运行在浏览器后台的脚本,它为网络应用提供了事件驱动的API,使得开发者可以创建更丰富的离线体验和实时功能。Service Worker 运行在它自己的线程中,因此不会影响页面的性能。以下是 Service Worker 的一些关键特性和用途:

  1. 离线体验: Service Worker 允许开发者缓存文件,这样即使用户处于离线状态,应用仍然可以工作。

  2. 消息推送和通知: Service Worker 支持推送消息,即使应用没有打开,也可以发送通知给用户。

  3. 后台同步: Service Worker 可以在后台同步数据,这对于需要保持数据最新状态的应用非常有用。

  4. 拦截网络请求: Service Worker 可以拦截对资源的网络请求,这意味着开发者可以决定如何响应这些请求,比如从缓存中提供资源或者从网络获取。

  5. 更新和部署: Service Worker 支持应用的更新和部署,可以控制资源的更新策略。

  6. 生命周期管理: Service Worker 有明确的生命周期,包括注册、安装、激活和注销阶段。

  7. 多页面共享: 一个 Service Worker 可以控制多个页面,这意味着它可以为整个应用提供统一的缓存和网络请求管理。

  8. 安全性: Service Worker 只能通过 HTTPS 来注册,这是为了保证通信的安全。

  9. 性能优化: Service Worker 可以用于性能优化,比如通过预加载资源来加快页面加载速度。

  10. Web 应用清单: Service Worker 通常与 Web 应用清单(Web App Manifest)一起使用,后者定义了应用的名称、图标、启动画面等信息。

Service Worker 的使用流程大致如下:

  • 注册:在页面的 JavaScript 中注册 Service Worker。
  • 安装:Service Worker 脚本被下载并执行安装阶段的代码。
  • 激活:安装完成后,Service Worker 被激活。
  • 控制:激活后,Service Worker 开始控制页面,并可以拦截网络请求。
  • 更新:浏览器会在后台检查 Service Worker 脚本的更新。
  • 注销:当不再需要 Service Worker 时,可以手动注销。

Service Worker生命周期:

  1. 注册(Register)

    通过 navigator.serviceWorker.register() 方法注册 Service Worker。如果注册成功,会返回一个 ServiceWorkerRegistration 对象,否则会抛出错误。
  2. 安装(Install)

    注册成功后,Service Worker 进入安装阶段。这个阶段会触发 install 事件,开发者可以在这个阶段执行初始化操作,如缓存必要的资源。install 事件的回调函数必须完成执行,Service Worker 才能进入下一个阶段。
  3. 等待(Wait)

    安装完成后,Service Worker 进入等待(waiting)状态。在这个阶段,Service Worker 还没有控制任何页面,直到所有旧版本的 Service Worker 控制的页面被关闭。
  4. 激活(Activate)

    当所有旧版本的 Service Worker 控制的页面都关闭后,或者通过 self.skipWaiting() 方法跳过等待阶段,Service Worker 进入激活(activating)状态。这个阶段会触发 activate 事件,通常用于清理旧缓存和旧资源。
  5. 控制(Control)

    激活完成后,Service Worker 开始控制页面,可以拦截和处理网络请求(fetch 事件)和接收来自页面的消息(message 事件)。
  6. 更新(Update)

    浏览器会定期检查 Service Worker 的更新。如果有新版本的 Service Worker 可用,它将进入一个新的安装周期,而旧版本的 Service Worker 将继续控制当前页面,直到它们被关闭。
  7. 注销(Unregister)

    当不再需要 Service Worker 时,可以通过 navigator.serviceWorker.getRegistration().then(registration => registration.unregister()) 方法注销 Service Worker。注销后,Service Worker 将停止工作,所有与之关联的缓存也会被清除。

生命周期示意图如下:

下面基于serviceworker写一个实际的案例来演示一下:

1.,我先基于nodejs创建一个server.js文件,搭建web服务器,以及一个api.json的文件数据,用于模拟后端网络请求,代码如下所示:

// server.js
const http = require("http");
const data = require("./api.json");

const delay = (wait = 1000) => {
  let startTime = Date.now();
  while (Date.now() - startTime < wait) {}
};

http
  .createServer((req, res) => {
    res.setHeader("Access-Control-Allow-Origin", "*");
    if (req.url === "/list") {
      // 针对/list接口,延迟3s返回数据
      delay(3000);
      res.write(JSON.stringify(data));
      res.end();
    } else {
      res.end("Hello World\n");
    }
  })
  .listen(3000, () => {
    console.log("服务运行在3000端口上");
  });
// api.json
[
  { "id": 1, "name": "abc" },
  { "id": 2, "name": "bcd" }
]

2.创建html文件,用于页面展示来测试serviceworker缓存效果

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>serverworker</title>
    <link rel="shortcut icon" href="#" />
</head>

<style>
    .box {
        padding: 20px;
        border: 1px solid #999;
    }

    #deleteBtn {
        display: none;
    }
</style>

<body>
    <div class="box">
        <button id="fetch-btn">获取数据</button>
        <button id="fetch-cache-btn">从缓存获取数据</button>
        <p>
            <span>接口数据:</span>
            <code></code>
            <button id="deleteBtn">删除</button>
        </p>

    </div>

</body>

<script>
    // 使用nodejs模拟后端接口,然后使用service-worker对接口进行离线缓存
    window.onload = async function () {
        const getData = () => {
            return fetch('http://localhost:3000/list').then(res => res.json()).then(data => {
                document.getElementsByTagName('code')[0].innerHTML = JSON.stringify(data);
                document.getElementById('deleteBtn').style.display = 'block';
            })
        }

        if ('serviceWorker' in navigator) {
            const registration = navigator.serviceWorker.register('./service-worker.js')
        }


        document.getElementById('fetch-cache-btn').addEventListener('click', function () {
            navigator.serviceWorker.controller.postMessage({ isCache: true });
            getData();
        })
        document.getElementById('fetch-btn').addEventListener('click', function () {
            navigator.serviceWorker.controller.postMessage({ isCache: false });
            getData();
        })


        document.getElementById('deleteBtn').onclick = function () {
            document.getElementsByTagName('code')[0].innerHTML = ''
            document.getElementById('deleteBtn').style.display = 'none';
        }
    }

</script>

</html>

3.紧接着,创建一个serviceworker.js文件,包括一些生命周期事件等逻辑;

// install事件:只有第一次加载会执行
self.addEventListener("install", function (event) {
  console.log("注册install事件");
  // skipWaiting:可防止出现等待情况,这意味着 Service Worker 在安装完后立即激活
  self.skipWaiting();
  // waitUntil:确保 Service Worker 脚本在完成某些操作后再继续执行后续的生命周期事件
  event.waitUntil(
    // 将/list路径缓存到v1下
    caches.open("v1").then(function (cache) {
      return cache.addAll(["http://localhost:3000/list"]);
    })
  );
});

// activate事件:激活serviceworker
self.addEventListener("activate", (event) => {
  event.waitUntil(self.clients.claim());
});

// fetch事件:监听、拦截fetch请求
self.addEventListener("fetch", async (event) => {
  console.log("拦截fetch", event);
  if (event.request.url.endsWith("/list") && self.isCache) {
    // 缓存
    // event.respondWith: 保证接口请求能够被serviceworker响应
    event.respondWith(
      caches.match(event.request).then(function (response) {
        if (response) {
          // 命中缓存,直接返回
          return response;
        } else {
          let request = event.request.clone();
          return fetch(request)
            .then(function (res) {
              if (!res || res?.status !== 200) {
                // 接口返回异常
                return res;
              }

              const responseClone = res.clone();
              caches.open("v1").then(function (cache) {
                console.log("重新设置缓存");
                cache.put(event.request, responseClone);
              });
            })
            .catch(function (reason) {
              console.error("ServiceWorker fetch failed: ", reason);
            });
        }
      })
    );
  } else {
    // 不缓存
    // 直接从网络获取资源
    event.respondWith(fetch(event.request));
  }
});

// message事件:接收来自主线程的消息,并往主线程发送消息
self.addEventListener("message", function (e) {
  self.isCache = e.data.isCache;
});

4.创建一个package.json用于启动server.js以及将html文件放在端口上运行(serviceworker运行必须要在端口上)

{
  "name": "serviceworker",
  "version": "1.0.0",
  "description": "",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "server": "nodemon server.js",
    "start": "http-server -a 127.0.0.1 -p 10000 & npm run server"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

5.启动 `npm run start`,页面展示效果如下:分别点击【获取数据按钮】 以及 【从缓存获取数据按钮】,可以发现获取数据 - 接口数据3s返回,从缓存获取数据 - 接口数据立即返回;

这是因为在fetch事件,对接口进行了拦截和缓存,我们可以F12看到接口缓存的详细信息

不愿意手动复制代码的同学们,可以clone我的github下载测试哈

github地址:GitHub - jackywq/serviceworker-demo: 基于serviceworker一个简单的接口缓存案例

有用的话,别忘记给github点个赞,谢谢哈!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值