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

参考文档

相关推荐
Nan_Shu_61411 分钟前
学习:uniapp全栈微信小程序vue3后台(28)
前端·学习·微信小程序·小程序·uni-app
珍宝商店21 分钟前
原生 JavaScript 方法实战指南
开发语言·前端·javascript
蓝莓味的口香糖32 分钟前
【企业微信】VUE项目在企微中自定义转发内容
前端·vue.js·企业微信
IT_陈寒32 分钟前
告别低效!用这5个Python技巧让你的数据处理速度提升300% 🚀
前端·人工智能·后端
—Qeyser34 分钟前
Laravel + UniApp AES加密/解密
前端·uni-app·laravel
C++chaofan36 分钟前
游标查询在对话历史场景下的独特优势
java·前端·javascript·数据库·spring boot
cg.family38 分钟前
Vue3 v-slot 详解与示例
前端·javascript·vue.js
FreeBuf_1 小时前
新型域名前置攻击利用Google Meet、YouTube、Chrome及GCP构建流量隧道
前端·chrome
c0detrend1 小时前
技术架构设计:如何打造一个高性能的Chrome截图插件
前端·chrome
幽络源小助理1 小时前
8、幽络源微服务项目实战:前端登录跨域同源策略处理+axios封装+权限的递归查询增删改+鉴权测试
前端·微服务·架构