Service Worker、PWA 与 Web Worker — 离线缓存与主线程算力分离

系列文章目录

《JavaScript 基础与进阶笔记》(前期偏基础巩固与常见面试点,后续进入闭包、异步、工程化等进阶主题)


文章目录

  • 系列文章目录
  • 前言
  • [一、PWA 是什么](#一、PWA 是什么)
  • [二、Service Worker 基础](#二、Service Worker 基础)
    • [2.1 定位](#2.1 定位)
    • [2.2 与 HTTP 缓存、存储的关系](#2.2 与 HTTP 缓存、存储的关系)
    • [2.3 注册(页面侧)](#2.3 注册(页面侧))
  • 三、生命周期(面试必背)
  • [四、Cache API 与常见策略](#四、Cache API 与常见策略)
    • [4.1 基本 API](#4.1 基本 API)
    • [4.2 三种经典策略](#4.2 三种经典策略)
    • [4.3 只缓存 GET](#4.3 只缓存 GET)
  • [五、更新、skipWaiting 与 clients.claim](#五、更新、skipWaiting 与 clients.claim)
    • [5.1 浏览器如何发现更新](#5.1 浏览器如何发现更新)
    • [5.2 为何新版本常「挂着不生效」](#5.2 为何新版本常「挂着不生效」)
    • [5.3 立刻生效(慎用但要会讲)](#5.3 立刻生效(慎用但要会讲))
  • [六、Web App Manifest](#六、Web App Manifest)
  • 七、离线页与推送(了解)
  • [八、Service Worker vs Web Worker(总览)](#八、Service Worker vs Web Worker(总览))
  • [九、Web Worker 基础](#九、Web Worker 基础)
    • [9.1 创建与通信](#9.1 创建与通信)
    • [9.2 适用与不适用](#9.2 适用与不适用)
  • [十、结构化克隆与 Transferable](#十、结构化克隆与 Transferable)
  • [十一、内联 Worker(可选)](#十一、内联 Worker(可选))
  • 十二、易混淆点归纳
  • 十三、思考与练习
  • 总结

前言

第 25 篇的 HTTP 缓存 由浏览器按响应头自动处理;第 24 篇的 IndexedDB 适合业务数据持久化。本篇把浏览器里两类 Worker 放在一起:Service Worker(SW) 负责离线缓存与 fetch 拦截,叠 Manifest 构成 PWAWeb Worker(WW) 负责把 CPU 密集计算 挪出主线程,避免卡顿。二者都不能操作 DOM,但职责完全不同------面试常混,本篇用对比表 + 各自最小示例讲清。


一、PWA 是什么

PWA 不是新框架,而是一组 Web 能力组合,让站点更接近原生 App 体验:

能力 作用
Service Worker 离线缓存、请求拦截、推送(可选)
Web App Manifest 名称、图标、主题色、全屏/独立窗口展示
HTTPS SW 注册的安全前提(localhost 除外)
可安装 满足条件时浏览器提示「添加到主屏幕」

与纯 SPA 的区别:断网后仍可能打开壳页面;安装后可有独立图标与 display: standalone


二、Service Worker 基础

2.1 定位

  • 运行在 浏览器后台 的独立线程,不能操作 DOM
  • 生命周期 独立于页面:关掉标签页,已激活的 SW 仍可存在。
  • 通过监听 install / activate / fetch 等事件,配合 caches 管理离线资源。

2.2 与 HTTP 缓存、存储的关系

HTTP 缓存(第 25 篇) Cache API(SW) localStorage / IndexedDB(第 24 篇)
谁控制 浏览器按响应头 你在 SW 里写策略 页面 JS 读写
典型内容 带 hash 的静态资源 离线壳、兜底页、可缓存 API 业务数据、配置
断网 强缓存命中才可能用 可完全离线返回缓存 本地仍有数据,但页面可能加载失败

2.3 注册(页面侧)

javascript 复制代码
if ("serviceWorker" in navigator) {
  window.addEventListener("load", () => {
    navigator.serviceWorker
      .register("/sw.js", { scope: "/" })
      .then((reg) => console.log("SW 注册成功", reg.scope))
      .catch((err) => console.error("SW 注册失败", err));
  });
}

要点:

  1. sw.js 必须与站点同源 ,且落在 scope 路径下(默认注册脚本所在目录)。
  2. HTTPShttp://localhost 可注册。
  3. 注册是 异步 的;首次访问不会立刻拦截,要等 install → activate 完成。

三、生命周期(面试必背)

#mermaid-svg-NsnnlC7PiqKiu9wL{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NsnnlC7PiqKiu9wL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NsnnlC7PiqKiu9wL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NsnnlC7PiqKiu9wL .error-icon{fill:#552222;}#mermaid-svg-NsnnlC7PiqKiu9wL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NsnnlC7PiqKiu9wL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NsnnlC7PiqKiu9wL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NsnnlC7PiqKiu9wL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NsnnlC7PiqKiu9wL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NsnnlC7PiqKiu9wL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NsnnlC7PiqKiu9wL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NsnnlC7PiqKiu9wL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NsnnlC7PiqKiu9wL .marker.cross{stroke:#333333;}#mermaid-svg-NsnnlC7PiqKiu9wL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NsnnlC7PiqKiu9wL p{margin:0;}#mermaid-svg-NsnnlC7PiqKiu9wL defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-NsnnlC7PiqKiu9wL g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-NsnnlC7PiqKiu9wL g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-NsnnlC7PiqKiu9wL g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-NsnnlC7PiqKiu9wL g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-NsnnlC7PiqKiu9wL g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-NsnnlC7PiqKiu9wL .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-NsnnlC7PiqKiu9wL .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-NsnnlC7PiqKiu9wL .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-NsnnlC7PiqKiu9wL .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-NsnnlC7PiqKiu9wL .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-NsnnlC7PiqKiu9wL .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-NsnnlC7PiqKiu9wL .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-NsnnlC7PiqKiu9wL .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NsnnlC7PiqKiu9wL .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NsnnlC7PiqKiu9wL .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NsnnlC7PiqKiu9wL .edgeLabel .label text{fill:#333;}#mermaid-svg-NsnnlC7PiqKiu9wL .label div .edgeLabel{color:#333;}#mermaid-svg-NsnnlC7PiqKiu9wL .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-NsnnlC7PiqKiu9wL .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-NsnnlC7PiqKiu9wL .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-NsnnlC7PiqKiu9wL .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-NsnnlC7PiqKiu9wL .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-NsnnlC7PiqKiu9wL .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NsnnlC7PiqKiu9wL .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NsnnlC7PiqKiu9wL #statediagram-barbEnd{fill:#333333;}#mermaid-svg-NsnnlC7PiqKiu9wL .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NsnnlC7PiqKiu9wL .cluster-label,#mermaid-svg-NsnnlC7PiqKiu9wL .nodeLabel{color:#131300;}#mermaid-svg-NsnnlC7PiqKiu9wL .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-NsnnlC7PiqKiu9wL .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-NsnnlC7PiqKiu9wL .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-NsnnlC7PiqKiu9wL .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-NsnnlC7PiqKiu9wL .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-NsnnlC7PiqKiu9wL .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-NsnnlC7PiqKiu9wL .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-NsnnlC7PiqKiu9wL .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-NsnnlC7PiqKiu9wL .note-edge{stroke-dasharray:5;}#mermaid-svg-NsnnlC7PiqKiu9wL .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-NsnnlC7PiqKiu9wL .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-NsnnlC7PiqKiu9wL .statediagram-note text{fill:black;}#mermaid-svg-NsnnlC7PiqKiu9wL .statediagram-note .nodeLabel{color:black;}#mermaid-svg-NsnnlC7PiqKiu9wL .statediagram .edgeLabel{color:red;}#mermaid-svg-NsnnlC7PiqKiu9wL #dependencyStart,#mermaid-svg-NsnnlC7PiqKiu9wL #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-NsnnlC7PiqKiu9wL .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NsnnlC7PiqKiu9wL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 下载 sw.js
register
install 成功
无旧 SW 或 skipWaiting
activate 成功
被新版本替换
parsed
installing
installed
activating
activated
redundant

阶段 事件 常见工作
下载解析 --- 浏览器对比 sw.js 字节,有变化则视为 新版本
installing install caches.open + addAll 预缓存静态资源
installed --- 若已有旧 SW 控制页面,新 SW 等待waiting
activating activate 删除旧版本 cachesclients.claim()
activated fetch / push... 拦截网络请求
redundant --- 被新 SW 取代后进入
javascript 复制代码
/* sw.js --- 最小骨架 */
const CACHE = "app-v1";
const PRECACHE = ["/", "/index.html", "/offline.html", "/app.js", "/style.css"];

self.addEventListener("install", (event) => {
  event.waitUntil(
    caches.open(CACHE).then((cache) => cache.addAll(PRECACHE))
  );
  /* 可选:不等旧 SW 关闭,立刻进入 activate(见第五节) */
  // self.skipWaiting();
});

self.addEventListener("activate", (event) => {
  event.waitUntil(
    caches.keys().then((keys) =>
      Promise.all(
        keys.filter((k) => k !== CACHE).map((k) => caches.delete(k))
      )
    )
  );
  /* 可选:立刻接管已打开页面 */
  // return self.clients.claim();
});

self.addEventListener("fetch", (event) => {
  event.respondWith(
    caches.match(event.request).then((cached) => cached ?? fetch(event.request))
  );
});

event.waitUntil(promise):告诉浏览器「等这个 Promise 完成再进入下一阶段」,否则 install/activate 可能失败。


四、Cache API 与常见策略

4.1 基本 API

javascript 复制代码
/* 在 SW 内使用 */
const cache = await caches.open("app-v2");
await cache.add("/logo.png");           /* 请求并缓存 */
await cache.addAll(["/a.js", "/b.css"]); /* 任一失败则整批失败 */
await cache.put(request, response);     /* 手动放入(常配合 res.clone()) */
const hit = await caches.match(request);
await caches.delete("app-v1");
const names = await caches.keys();

Response 只能读一次 :要写进 cache 必须 response.clone()

4.2 三种经典策略

策略 流程 适用
Cache First 先查缓存,没有再网络 带 hash 的 JS/CSS、字体、图片
Network First 先网络,失败再用缓存 需实时的 API、用户信息
Stale While Revalidate 先返缓存,同时后台更新缓存 可接受略旧但要快的内容
javascript 复制代码
/* Cache First */
self.addEventListener("fetch", (event) => {
  event.respondWith(
    caches.match(event.request).then(
      (cached) =>
        cached ??
        fetch(event.request).then((res) => {
          if (!res || res.status !== 200 || res.type !== "basic") return res;
          const clone = res.clone();
          caches.open(CACHE).then((c) => c.put(event.request, clone));
          return res;
        })
    )
  );
});

/* Network First */
self.addEventListener("fetch", (event) => {
  event.respondWith(
    fetch(event.request)
      .then((res) => {
        const clone = res.clone();
        caches.open(CACHE).then((c) => c.put(event.request, clone));
        return res;
      })
      .catch(() => caches.match(event.request))
  );
});

工程上可用 Workbox (Google 维护)封装上述策略,减少手写分支;原理仍是 fetch 事件 + caches

4.3 只缓存 GET

fetch 事件里对 非 GET (POST 等)一般 不要 乱缓存,直接 return fetch(event.request),避免脏数据。


五、更新、skipWaiting 与 clients.claim

5.1 浏览器如何发现更新

  • sw.js 文件本身 按「最多 24 小时检查一次 + 页面刷新/导航」等规则重新请求;有字节差异 才算新版本。
  • 预缓存里的 app.js 更新了,若 sw.js 没变 ,SW 不会 自动重装------通常要 CACHE 版本名 或改 sw.js 触发更新。

5.2 为何新版本常「挂着不生效」

已有 activated 的旧 SW 控制页面时,新 SW install 完成后会进入 waiting ,直到 所有受控标签页关闭 ,新 SW 才会 activate

5.3 立刻生效(慎用但要会讲)

javascript 复制代码
/* sw.js --- install 里 */
self.addEventListener("install", (event) => {
  self.skipWaiting(); /* 跳过 waiting,直接 activate */
  event.waitUntil(/* 预缓存... */);
});

self.addEventListener("activate", (event) => {
  event.waitUntil(
    (async () => {
      await deleteOldCaches();
      await self.clients.claim(); /* 立刻控制当前打开的页面 */
    })()
  );
});

页面侧监听刷新:

javascript 复制代码
navigator.serviceWorker.addEventListener("controllerchange", () => {
  window.location.reload(); /* 或提示用户「新版本可用」 */
});

面试句skipWaiting 让新 SW 不等关页;clients.claim 让新 SW 立即接管;常配合 controllerchange 提示刷新。


六、Web App Manifest

在 HTML 中引入:

html 复制代码
<link rel="manifest" href="/manifest.webmanifest" />

manifest.webmanifest 示例:

json 复制代码
{
  "name": "我的应用",
  "short_name": "App",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#F48FB1",
  "icons": [
    { "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any maskable" }
  ]
}
字段 含义
display: standalone 无浏览器地址栏,类似 App
start_url 从桌面图标启动的入口
theme_color 状态栏/标题栏色调
icons 安装到桌面所需,建议 192 + 512

可安装性 (Chrome 等)大致需要:HTTPS、有效 manifest、已注册 SW、start_url 可访问、满足图标尺寸等(以 Lighthouse PWA 审计为准)。


七、离线页与推送(了解)

javascript 复制代码
/* 网络失败时返回离线页 */
self.addEventListener("fetch", (event) => {
  if (event.request.mode !== "navigate") return;

  event.respondWith(
    fetch(event.request).catch(() => caches.match("/offline.html"))
  );
});

Push 通知 :SW 监听 pushregistration.showNotification(),需服务端 VAPID 等,与第 26 篇 SSE/WebSocket 是不同通道;业务 IM 仍多用 WebSocket,系统级提醒才用 Push。


八、Service Worker vs Web Worker(总览)

Service Worker Web Worker
目的 代理网络、离线、推送 算力 offload,不阻塞 UI
注册/创建 navigator.serviceWorker.register new Worker(url)
与页面 scope 控制多个标签 通常 一页一个 Dedicated 实例
生命周期 install → activate → redundant 页面 terminate() 或关页结束
DOM
HTTPS 必须(localhost 除外) 脚本需 同源(或 CORS)
典型 API fetchcachesclients postMessagefetch、IndexedDB

口诀SW 管「网」;WW 管「算」。小任务(算几个数)用 Worker 反而有创建与通信开销,不必滥用。


九、Web Worker 基础

9.1 创建与通信

javascript 复制代码
/* main.js --- 主线程 */
const worker = new Worker("/worker.js", { type: "module" }); /* 可选 ES Module */

worker.onmessage = ({ data }) => {
  console.log("Worker 返回:", data);
};

worker.onerror = (e) => console.error(e.message);

/* 发送数据(默认结构化克隆 = 深拷贝) */
worker.postMessage({ n: 42 });

/* 用完释放 */
worker.terminate();
javascript 复制代码
/* worker.js --- Worker 线程(全局是 self,没有 window/document) */
self.onmessage = ({ data }) => {
  const result = heavyCompute(data);
  self.postMessage({ result });
};

function heavyCompute({ n }) {
  /* 大数组排序、加解密、解析 CSV 等放这里 */
  return n * n;
}

要点:

  1. Worker 里可用 fetchIndexedDBWebSocket 等,不能 碰 DOM。
  2. 与主线程只有 postMessage / message / error 通道。
  3. type: "module" 时 Worker 内可 import,需构建工具或浏览器支持。

9.2 适用与不适用

适合 不适合
大数组排序/统计、文件哈希、图像像素处理 轻量逻辑、频繁碎消息
解析超大 CSV/JSON 文本 需要直接改 DOM 的 UI 逻辑
加密、压缩等 CPU 密集 替代 SW 做离线缓存

十、结构化克隆与 Transferable

postMessage 的第二个参数可传 Transferable ,把 ArrayBuffer 等所有权 转移 给 Worker,主线程不再能访问,避免大数组双份拷贝。

javascript 复制代码
const arr = new Float64Array(1_000_000);
arr.fill(1);

const worker = new Worker("/worker.js");

/* 转移 buffer,零拷贝;主线程 arr.buffer 变为 detached */
worker.postMessage({ buffer: arr.buffer }, [arr.buffer]);

worker.onmessage = () => console.log("排序完成");
javascript 复制代码
/* worker.js */
self.onmessage = ({ data: { buffer } }) => {
  const view = new Float64Array(buffer);
  view.sort();
  self.postMessage({ done: true }, [buffer]); /* 也可转回主线程 */
};
结构化克隆(默认) Transferable
数据 深拷贝一份 所有权转移,原侧不可用
开销 大对象贵 ArrayBuffer 更合适
可传类型 多数对象、TypedArray ArrayBufferMessagePort

SharedWorker (了解):多标签 共享 同一 Worker 实例,通过 new SharedWorker(url) + port.postMessage,用于跨页共享连接或状态;日常 Dedicated Worker 更常见。


十一、内联 Worker(可选)

不便单独 worker.js 时,可用 Blob URL 动态创建(注意 CSP 可能限制 blob:):

javascript 复制代码
const code = `
  self.onmessage = ({ data }) => self.postMessage(data * 2);
`;
const blob = new Blob([code], { type: "application/javascript" });
const url = URL.createObjectURL(blob);
const inline = new Worker(url);
inline.postMessage(21);
inline.onmessage = ({ data }) => console.log(data); /* 42 */
URL.revokeObjectURL(url);

十二、易混淆点归纳

Service Worker

  1. SW 必须 HTTPSlocalhost 例外);HTTP 线上无法注册。
  2. HTTP 强缓存Cache API 两套机制;API 数据慎用 Cache First。
  3. caches 不会自动过期 ,要在 activate 里按版本号删旧 cache。
  4. app.[hash].js 不算更新 SW ;要 bump CACHE 或改 sw.js
  5. res.clone()put,否则 body 被读空。
  6. 新 SW 默认 waiting ;立刻生效靠 skipWaiting + clients.claim

Web Worker

  1. WW 不能操作 DOM ,也不能用 SW 的 caches 拦截全站请求
  2. postMessage 默认拷贝 ;大 buffer 用 Transferable,否则双倍内存与时间。
  3. Worker 脚本需同源 ;跨域要正确 CORS ,不能像 <script src> 随便引外站。
  4. Worker 是 线程 不是独立进程,仍占内存;任务结束应 terminate()

十三、思考与练习

1. 为何 Service Worker 要求 HTTPS?

解析:SW 能 拦截全站请求 ,中间人若可注入 SW 则危害极大;HTTPS 降低劫持风险(本地开发放行 localhost)。

2. 用户一直不关闭标签页,发版后如何让用户用到新 SW?

解析:installskipWaiting()activateclients.claim() ,页面监听 controllerchange 提示刷新;或接受「关页后生效」。

3. Cache First 适合缓存登录后的 /api/user 吗?

解析:一般不适合 ;用户相关接口宜 Network First 或仅短时 SWR,避免读到他人旧数据。

4. addAll 里有一个文件 404,会怎样?

解析:整批 install 失败 ,SW 可能处于异常状态;预缓存列表要确认真存在,或改为逐个 add 并 catch。

5. PWA 与「把网站加到手机桌面」是一回事吗?

解析:桌面快捷方式可以只是书签;PWA 安装 还需 manifest、SW 等满足 可安装性 条件,体验更接近 App。

6. 为何大数组排序适合放 Web Worker,而不是 Service Worker?

解析:排序是 CPU 计算 ,应放 Web Worker 避免卡主线程;Service Worker网络拦截与缓存,不负责替你算数组。

7. postMessage(bigArray)postMessage(bigArray.buffer, [bigArray.buffer]) 有何区别?

解析:前者 结构化克隆 整份拷贝;后者 转移 ArrayBuffer 所有权,主线程侧 buffer detached,适合大数据。


总结

  • PWA = Service Worker + Manifest + HTTPS(+ 可安装)。
  • SW :生命周期 install → activate → fetch ;策略 Cache / Network First ;更新 skipWaiting / claim
  • Web WorkerpostMessage 通信;大计算 offload;大 buffer 用 Transferable
  • SW 管网,WW 管算 ;与第 25 篇 HTTP 缓存、第 24 篇 IndexedDB 分工互补

下一篇讲 浏览器高级 APIMutationObserverResizeObserverpostMessage 跨窗口(系列第 28 篇,大纲 §28)。

相关推荐
JustHappy1 小时前
古法编程秘籍(四):函数究竟是什么?把函数最重要的能力一次讲清楚
前端·后端·面试
OpenTiny社区1 小时前
一行命令添加 AI 对话入口!TinyRobot 也太省事了~
前端·vue.js·ai编程
sagima_sdu1 小时前
Vue 前端径向渐变背景制作
前端·javascript·vue.js
叶落阁主2 小时前
Vue3 后台管理系统全局菜单搜索实战:Cmd/Ctrl + K、权限菜单与拼音过滤
前端·javascript·vue.js
卷帘依旧2 小时前
setState是同步的还是异步的
前端·面试
卷帘依旧2 小时前
讲一下useEffect和useLayoutEffect
前端·面试
wuhen_n2 小时前
AI Agent 入门:从零实现 LangChain 基础智能体
前端·langchain·ai编程
MacroZheng2 小时前
阿里Qoder + GLM-5.1,夯爆了!
前端·vue.js·人工智能
我是小胡胡2 小时前
彦火APP-Flutter包体分析
前端