简单介绍serviceWorker使用场景

serviceWorker 是什么

Service workers essentially act as proxy servers that sit between web applications, the browser, and the network

Service Worker 本质上充当位于 Web 应用程序、浏览器和网络(如果可用)之间的代理服务器。

使用场景介绍

1. 静态资源缓存与优化(Cache-First / Stale-While-Revalidate)

index.html

html 复制代码
  <script>
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker
        .register('/cache-static-sw.js')
        .then((registration) => {
          console.log('[SW] Service Worker registered with scope:', registration.scope);
        })
        .catch((error) => {
          console.error('[SW] Service Worker registration failed:', error);
        });
    }
  </script>

sw.js

javascript 复制代码
/* eslint-disable no-restricted-globals */
const CACHE_NAME_PUBLIC = 'cdn-static-cache-v1';
const CACHE_NAME_VENDORS = 'cdn-static-cache-vendors-v1';

// 匹配规则
const PUBLIC_REGEX = /^https:\/\/cdn-static\.scm321\.com\/public\/.*\.(js|css)/;
const VENDORS_REGEX = /^https:\/\/cdn-static\.scm321\.com\/scm-saas-web\/.*\.(js|css)/;

self.addEventListener('install', (event) => {
  console.debug('[SW] Installed');
  // 跳过等待,直接激活
  // 正常流程如下
  // 1. 浏览器检测到 Service Worker 文件有变化。
  // 2. 下载新版本 → 触发 `install` 事件 → 进入 `waiting` 状态。
  // 3. 只有当所有旧版控制的页面都关闭后,新版才会被激活。
  // 4. 新版激活后 → 触发 `activate` 事件 → 开始接管新的页面。
  // ### `self.skipWaiting()` 的作用:
  // 1. 强制让处于 waiting 状态的 Service Worker 立即激活。
  // 2. 不再等待旧版控制的页面全部关闭。
  // 3. 新版会马上进入 `activate` 阶段,但是旧页面(旧 clients)仍由旧版 SW 控制,直到这些页面刷新或重新加载。
  self.skipWaiting();
});

self.addEventListener('activate', (event) => {
  console.debug('[SW] Activated');
  // 等待客户端接管
  // self.clients.claim() 作用
  // 正常流程如下
  // 新版 Service Worker 激活后(`activate` 事件触发),
  // 默认情况下,它只会接管**:
  // 1. 之后新打开的页面,
  // 2.已被强制刷新的页面。
  // 3. 旧页面(clients)仍然被旧版 SW 控制,直到用户刷新它们。
  // ### `self.clients.claim()` 解决的问题:
  // 让激活的 SW 直接接管所有正在打开的页面(clients) ,无需用户手动刷新。
  // 适合实现「即时生效」的场景,比如:
  // 1. 页面首次打开就受新版 SW 控制。
  // 2。 静态缓存、缓存策略变更后立即接管。

  event.waitUntil(self.clients.claim());
});

self.addEventListener('fetch', (event) => {
  const { url } = event.request;

  if (PUBLIC_REGEX.test(url)) {
    event.respondWith(cacheFirst(event.request, CACHE_NAME_PUBLIC));
    return;
  }

  if (VENDORS_REGEX.test(url)) {
    event.respondWith(cacheFirst(event.request, CACHE_NAME_VENDORS));
  }
});

// CacheFirst 策略函数
async function cacheFirst(request, cacheName) {
  const cache = await caches.open(cacheName);
  const cachedResponse = await cache.match(request, { ignoreSearch: true });

  if (cachedResponse) {
    console.debug('[SW] Cache Hit:', request.url);
    return cachedResponse;
  }

  const networkResponse = await fetch(request);
  if (networkResponse.ok) {
    console.debug('[SW] Network Response OK:', request.url);
    cache.put(request, networkResponse.clone());
  }
  return networkResponse;
}

展示结果 缓存没有时显示如下

再次刷新

问题记录

Q. network中size列显示了ServiceWorker 但是时间时间没有减少

A. 这个问题说明serviceWorker代理已经生效,但是没有命中缓存,可在 timing 中查看正式的response信息,可能还是走到了兜底的networkResponse中

Q. 为什么本地测试已经生效,发版后没有生效
  • service-worker 要求必须是https;localhost浏览器为了调试方便也会认为是安全的;因此确认域名是否是https的
  • 如果访问的域名和service-worker注册的域名不一致,也就是跨域了,则资源的reponse必须设置允许跨域
  • 跨域时,发起请求的request mod 必须为cors(可以打印看下request的信息),也就是说是用script标签发起的请求 必须在 script 标签里添加crossorigin="anonymous"

2. 消息推送

  • 依赖浏览器厂商,国内基本不可用
  • 本地测试需要开启代理,而且不稳定,可以多试几次,只适合学习使用
  • github代码地址,成功效果如下图

总结

  1. 推送时系统相同tag只显示一次
  2. 离线推送一定要使用service-worker,因为浏览器关闭时js是无法执行的,而sw的注册是浏览器级别的,可以完成离线推送的能力
  3. 依赖浏览器运营商

🧭 各大浏览器推送服务提供商(Push Service URL 对应关系)

浏览器 推送服务运营商名称 Push Service URL 前缀
Chrome Google Firebase Cloud Messaging (FCM) https://fcm.googleapis.com/fcm/send/
Firefox Mozilla Push Service https://updates.push.services.mozilla.com/push/
Edge Microsoft Push Notification Service (MPNS) https://wns2-par02p.notify.windows.com/w/
Safari (macOS, iOS) Apple Push Notification Service (APNs) 原生推送系统,不使用标准 Web Push endpoint
Opera 基于 Chromium,使用 Google FCM https://fcm.googleapis.com/fcm/send/
Brave 使用 Google FCM https://fcm.googleapis.com/fcm/send/
Samsung Internet 使用 Google FCM https://fcm.googleapis.com/fcm/send/

3. 导航请求预加载(Navigation Preload)

  • 只针对html有效,适合ssr和pwa

4. 处理请求重复问题

参考文章 前端如何彻底解决重复请求问题

  1. 优点,网络请求都可以在network里面清楚的看到,方便查看堆栈信息
  2. 缺点:必须serviceWorker激活后才能生效

sw.js

javascript 复制代码
// service worker
// 使用 CDN 引入 js-sha256
importScripts(
  "https://cdn.jsdelivr.net/npm/js-sha256@0.9.0/build/sha256.min.js",
);

self.addEventListener("install", (event) => {
  console.debug("[SW] Installed");
  self.skipWaiting();
});

self.addEventListener("activate", (event) => {
  console.debug("[SW] Activated");
  event.waitUntil(self.clients.claim());
});
self.addEventListener("fetch", (event) => {
  if (event.request.url.includes("api")) {
    event.respondWith(handleFetch(event));
    return;
  }
});

const depsRequestCache = new Map();
const realRequestCache = new Map();

const handleFetch = async (event) => {
  const hash = sha256.create();
  hash.update(
    JSON.stringify({
      url: event.request.url,
      method: event.request.method,
      body: event.request.body,
    }),
  );
  const fetchKey = hash.hex().slice(0, 40);

  if (!realRequestCache.get(fetchKey)) {
    const promise = fetch(event.request).then((result) => {
      depsRequestCache.get(fetchKey)?.forEach((item) => {
        item.resolve(result.clone());
      });
      depsRequestCache.delete(fetchKey);
      realRequestCache.delete(fetchKey);
      return result;
    });
    realRequestCache.set(fetchKey, promise);
  }

  const deferred = {};
  deferred.promise = new Promise((resolve, reject) => {
    deferred.resolve = resolve;
    deferred.reject = reject;
  });

  depsRequestCache.set(fetchKey, [
    ...(depsRequestCache.get(fetchKey) || []),
    deferred,
  ]);

  event.request.signal.onabort = () => {
    depsRequestCache.get(fetchKey)?.forEach((item) => {
      item.reject(new Error("abort"));
    });
    depsRequestCache.delete(fetchKey);
  };

  return deferred.promise;
};

api介绍

  1. register
js 复制代码
register(scriptURL)
register(scriptURL, options)
  • 在同一个作用域(scope)下,Service Worker 只能注册一个。
  • 同一页面 只能有一个激活状态的 Service Worker(按作用域匹配最近的)。
js 复制代码
navigator.serviceWorker.register('/sw1.js', { scope: '/' });
navigator.serviceWorker.register('/sw2.js', { scope: '/' }); // 这会替换掉 sw1.js

navigator.serviceWorker.register('/sw-main.js', { scope: '/' });
navigator.serviceWorker.register('/sw-admin.js', { scope: '/admin/' });
navigator.serviceWorker.register('/sw-shop.js', { scope: '/shop/' });
  • 访问 / 时,只有 sw-main.js 生效。
  • 访问 /admin/ 时,sw-admin.js 生效。
  • 访问 /shop/ 时,sw-shop.js 生效。
  1. cacheStorage
  2. cache
  3. clients
  4. client

参考文档

相关推荐
赵庆明老师1 分钟前
vben开发入门5:vite.config.ts
前端·html·vue3·vben
qq_12084093711 分钟前
Three.js 工程向:实例化渲染 InstancedMesh 的批量优化
前端·javascript
起这个名字5 分钟前
LangGraphJs 核心概念、工作流程理解及应用
前端·人工智能
小赵同学WoW6 分钟前
vue组件基础知识
前端
牛奶15 分钟前
浏览器藏了这么多神器,你居然不知道?
前端·chrome·api
WebInfra20 分钟前
Rspack 2.0 正式发布!
前端·javascript·前端框架
极速蜗牛27 分钟前
Cursor最近变傻了?
前端
码字小学妹37 分钟前
Claude Opus 4.7 接入指南(2026):国内配置 + xhigh 推理 + 成本计算
前端
小赵同学WoW39 分钟前
插槽【vue2】与 【vue3】对比
前端
代码随想录39 分钟前
Agent大厂面试题汇总:ReAct、Function Calling、MCP、RAG高频问题
前端·react.js·前端框架