PWA (Progressive Web App)
PWA(渐进式 Web 应用)是一种理念和一组技术的集合,旨在让传统的 Web 应用具备类似 Native App(原生应用)的体验,如离线访问、添加到主屏幕、消息推送等。
在前端面试中,PWA 的考察重点绝大多数集中在 Service Worker 及其缓存策略上。
1. PWA 的三大核心技术
- Service Worker (SW):PWA 的灵魂。它是一个运行在浏览器背后的独立线程,充当 Web 应用与网络之间的代理服务器,能够拦截网络请求并进行缓存管理,从而实现离线访问。
- Web App Manifest (
manifest.json):一个 JSON 配置文件,告诉浏览器如何显示这个应用(应用名称、图标、启动画面、主题色、显示模式如全屏等),它是实现“添加到主屏幕”的基础。 - HTTPS:Service Worker 权限极大(能拦截所有请求),为了防止中间人攻击,PWA 强制要求必须在 HTTPS 环境下运行(本地开发
localhost除外)。
2. Service Worker 核心考点
2.1 Service Worker 的生命周期
这是面试最高频的考点,你需要清晰地描述出它的几个阶段:
- 解析/注册 (Register):在主线程 JS 中调用
navigator.serviceWorker.register()。 - 安装 (Install):注册成功后触发
install事件。通常在这个阶段预缓存核心静态资源(HTML/CSS/JS)。- 如果任何一个文件缓存失败,安装就会失败。
- 激活 (Activate):安装成功后触发
activate事件。通常在这个阶段清理旧版本的无用缓存。 - 运行/空闲 (Idle/Fetch):激活后,SW 开始接管页面的网络请求,监听
fetch事件。如果长时间不用,会被浏览器休眠。
// 1. 注册
if ("serviceWorker" in navigator) {
navigator.serviceWorker
.register("/sw.js")
.then((reg) => console.log("注册成功"));
}
// sw.js 内部
// 2. 安装阶段:缓存静态资源
self.addEventListener("install", (event) => {
event.waitUntil(
caches
.open("v1")
.then((cache) => cache.addAll(["/", "/index.html", "/style.css"])),
);
});
// 3. 激活阶段:清理旧缓存
self.addEventListener("activate", (event) => {
event.waitUntil(
caches.keys().then((keys) =>
Promise.all(
keys.map((key) => {
if (key !== "v1") return caches.delete(key);
}),
),
),
);
});
// 4. 拦截请求
self.addEventListener("fetch", (event) => {
event.respondWith(
caches
.match(event.request)
.then((response) => response || fetch(event.request)),
);
});
2.2 Service Worker 的更新机制(难点)
面试题:如果你修改了
sw.js的代码,浏览器是怎么更新它的?
- 浏览器在每次页面加载时,会在后台静默下载
sw.js。如果发现文件字节哪怕有一点点不同,就会认为有新版本。 - 新的 SW 会进入
install阶段,但在安装完成后,它会进入waiting(等待)状态,而不是立刻接管页面。 - 只有当所有使用旧版本 SW 的页面(Tab 页)都完全关闭,旧 SW 才会卸载,新 SW 才会进入
activate阶段并接管页面。 - 如何跳过等待强制更新? 在新的 SW 中调用
self.skipWaiting(),并配合页面侧的navigator.serviceWorker.addEventListener('controllerchange', () => window.location.reload())实现静默刷新。
3. 常见缓存策略 (Caching Strategies)
在 fetch 事件中,我们可以通过代码实现各种缓存策略。面试官常问:“对于不同的资源,你通常采用什么缓存策略?”
| 策略名称 | 思想描述 | 适用场景 |
|---|---|---|
| Cache First (缓存优先) | 优先查缓存,缓存有直接返回;没有才发网络请求。 | 不常变动的静态资源(图片、字体、带 Hash 的 JS/CSS)。 |
| Network First (网络优先) | 优先发网络请求,成功则更新缓存并返回;失败/离线时降级使用缓存。 | 实时性要求高的数据(如文章列表、用户信息)。 |
| Stale-While-Revalidate (过期重验证) | 最经典的 PWA 策略。先立刻返回缓存数据给页面,同时在后台发起网络请求更新缓存。 | 头像、列表等:允许用户先看到旧数据,随后无感刷新为新数据。 |
| Network Only (仅网络) | 不走缓存,直接发起网络请求。 | 支付接口、提交表单等绝对不能缓存的请求。 |
| Cache Only (仅缓存) | 只从缓存读取,没有就报错。 | 特定离线包场景。 |
4. 数据存储:CacheStorage vs IndexedDB
在 PWA 中,我们通常使用 CacheStorage(通过 caches API 访问)和 IndexedDB。
面试题:Service Worker 中可以用 LocalStorage 吗? 答:绝对不能。 Service Worker 中无法访问 DOM,且所有 API 都必须是异步的(Promise)。LocalStorage 是同步阻塞 API,在 SW 中不可用。
- CacheStorage:专门用来缓存 HTTP 的 Request/Response 对象。适合存 HTML, CSS, JS, 图片等静态资源。
- IndexedDB:浏览器本地的非关系型数据库。适合存大量的业务 JSON 数据(如文章列表、聊天记录)。
5. PWA 的优缺点(对比 React Native / Flutter)
面试题:有了 React Native 和小程序,为什么还需要 PWA?
优点:
- 开发成本极低:一套 Web 代码到处跑,无需学习新语言(如 Dart),无需学习原生 API。
- 免安装、即点即用:无需经过 App Store / Google Play 漫长的审核,用户只需访问 URL 即可添加到桌面。
- SEO 友好:本质还是 Web,内容可被搜索引擎抓取。
缺点(痛点):
- 系统生态支持不一:Android 对 PWA 支持完美;但 iOS (Safari) 对 PWA 支持一直比较消极(比如曾长期不支持 Web Push 消息推送,直到 iOS 16.4 才逐步开放),这极大地限制了 PWA 的普及。
- 硬件访问受限:无法像 RN/Flutter 那样调用底层的蓝牙、NFC、高级相机特性等。
- 性能上限:依然是跑在浏览器内核里,渲染复杂动画和长列表的性能依然不如 RN 等跨端方案。