系列文章目录
《JavaScript 基础与进阶笔记》(前期偏基础巩固与常见面试点,后续进入闭包、异步、工程化等进阶主题)
- 第 01 篇:数据类型与类型判断
- 第 02 篇:变量声明与作用域
- 第 03 篇:闭包与高阶函数
- 第 04 篇:函数工厂
- 第 05 篇:this 指向与绑定
- 第 06 篇:原型与原型链
- 第 07 篇:类与继承
- 第 08 篇:JS 执行机制与异步队列
- 第 09 篇:数组常用方法
- 第 10 篇:字符串算法
- 第 11 篇:常见手写题合集(上)
- 第 12 篇:常见手写题合集(下)
- 第 13 篇:Promise 与 async/await
- 第 14 篇:数据结构基础
- 第 15 篇:垃圾回收与内存
- 第 16 篇:DOM 基础全面解析
- 第 17 篇:DOM 性能与渲染
- 第 18 篇:DOM 交互补充
- 第 19 篇:DOM 实战案例
- 第 20 篇:CSS 布局与可视化高频
- 第 21 篇:移动端与 viewport
- 第 22 篇:BOM 核心对象
- 第 23 篇:前端路由原理
- 第 24 篇:浏览器存储对比
- 第 25 篇:网络与跨域
- 第 26 篇:网络请求与实时通道
- 第 27 篇:Service Worker、PWA 与 Web Worker(./系列文章-27-Service Worker、PWA 与 Web Worker.md)(本文)
文章目录
- 系列文章目录
- 前言
- [一、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 构成 PWA ;Web 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));
});
}
要点:
sw.js必须与站点同源 ,且落在scope路径下(默认注册脚本所在目录)。- 仅 HTTPS 或
http://localhost可注册。 - 注册是 异步 的;首次访问不会立刻拦截,要等 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 |
删除旧版本 caches、clients.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 监听 push → registration.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 | fetch、caches、clients |
postMessage、fetch、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;
}
要点:
- Worker 里可用
fetch、IndexedDB、WebSocket等,不能 碰 DOM。 - 与主线程只有
postMessage/message/error通道。 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 | ArrayBuffer、MessagePort 等 |
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
- SW 必须 HTTPS (
localhost例外);HTTP 线上无法注册。 - HTTP 强缓存 与 Cache API 两套机制;API 数据慎用 Cache First。
caches不会自动过期 ,要在activate里按版本号删旧 cache。- 改
app.[hash].js不算更新 SW ;要 bumpCACHE名 或改sw.js。 res.clone()再put,否则 body 被读空。- 新 SW 默认 waiting ;立刻生效靠
skipWaiting+clients.claim。
Web Worker
- WW 不能操作 DOM ,也不能用 SW 的
caches拦截全站请求。 postMessage默认拷贝 ;大 buffer 用 Transferable,否则双倍内存与时间。- Worker 脚本需同源 ;跨域要正确 CORS ,不能像
<script src>随便引外站。 - Worker 是 线程 不是独立进程,仍占内存;任务结束应
terminate()。
十三、思考与练习
1. 为何 Service Worker 要求 HTTPS?
解析:SW 能 拦截全站请求 ,中间人若可注入 SW 则危害极大;HTTPS 降低劫持风险(本地开发放行 localhost)。
2. 用户一直不关闭标签页,发版后如何让用户用到新 SW?
解析:install 里 skipWaiting() ,activate 里 clients.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 Worker :postMessage 通信;大计算 offload;大 buffer 用 Transferable。
- SW 管网,WW 管算 ;与第 25 篇 HTTP 缓存、第 24 篇 IndexedDB 分工互补。
下一篇讲 浏览器高级 API :MutationObserver、ResizeObserver、postMessage 跨窗口(系列第 28 篇,大纲 §28)。