简单介绍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

参考文档

相关推荐
卑微前端在线挨打5 分钟前
2025数字马力一面面经(社)
前端
OpenTiny社区19 分钟前
一文解读“Performance面板”前端性能优化工具基础用法!
前端·性能优化·opentiny
拾光拾趣录41 分钟前
🔥FormData+Ajax组合拳,居然现在还用这种原始方式?💥
前端·面试
不会笑的卡哇伊1 小时前
新手必看!帮你踩坑h5的微信生态~
前端·javascript
bysking1 小时前
【28 - 记住上一个页面tab】实现一个记住用户上次点击的tab,上次搜索过的数据 bysking
前端·javascript
Dream耀1 小时前
跨域问题解析:从同源策略到JSONP与CORS
前端·javascript
前端布鲁伊1 小时前
【前端高频面试题】面试官: localhost 和 127.0.0.1有什么区别
前端
HANK1 小时前
Electron + Vue3 桌面应用开发实战指南
前端·vue.js
胤祥矢量商铺1 小时前
菜鸟笔记007 [...c(e), ...d(i)]数组的新用法
c语言·开发语言·javascript·笔记·illustrator插件
極光未晚1 小时前
Vue 前端高效分包指南:从 “卡成 PPT” 到 “丝滑如德芙” 的蜕变
前端·vue.js·性能优化