Skip to main content

PWA (Progressive Web App)

PWA(渐进式 Web 应用)是一种理念和一组技术的集合,旨在让传统的 Web 应用具备类似 Native App(原生应用)的体验,如离线访问添加到主屏幕消息推送等。

在前端面试中,PWA 的考察重点绝大多数集中在 Service Worker 及其缓存策略上。


1. PWA 的三大核心技术

  1. Service Worker (SW):PWA 的灵魂。它是一个运行在浏览器背后的独立线程,充当 Web 应用与网络之间的代理服务器,能够拦截网络请求并进行缓存管理,从而实现离线访问。
  2. Web App Manifest (manifest.json):一个 JSON 配置文件,告诉浏览器如何显示这个应用(应用名称、图标、启动画面、主题色、显示模式如全屏等),它是实现“添加到主屏幕”的基础。
  3. HTTPS:Service Worker 权限极大(能拦截所有请求),为了防止中间人攻击,PWA 强制要求必须在 HTTPS 环境下运行(本地开发 localhost 除外)。

2. Service Worker 核心考点

2.1 Service Worker 的生命周期

这是面试最高频的考点,你需要清晰地描述出它的几个阶段:

  1. 解析/注册 (Register):在主线程 JS 中调用 navigator.serviceWorker.register()
  2. 安装 (Install):注册成功后触发 install 事件。通常在这个阶段预缓存核心静态资源(HTML/CSS/JS)。
    • 如果任何一个文件缓存失败,安装就会失败。
  3. 激活 (Activate):安装成功后触发 activate 事件。通常在这个阶段清理旧版本的无用缓存。
  4. 运行/空闲 (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 的代码,浏览器是怎么更新它的?

  1. 浏览器在每次页面加载时,会在后台静默下载 sw.js。如果发现文件字节哪怕有一点点不同,就会认为有新版本。
  2. 新的 SW 会进入 install 阶段,但在安装完成后,它会进入 waiting(等待)状态,而不是立刻接管页面。
  3. 只有当所有使用旧版本 SW 的页面(Tab 页)都完全关闭,旧 SW 才会卸载,新 SW 才会进入 activate 阶段并接管页面。
  4. 如何跳过等待强制更新? 在新的 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?

优点:

  1. 开发成本极低:一套 Web 代码到处跑,无需学习新语言(如 Dart),无需学习原生 API。
  2. 免安装、即点即用:无需经过 App Store / Google Play 漫长的审核,用户只需访问 URL 即可添加到桌面。
  3. SEO 友好:本质还是 Web,内容可被搜索引擎抓取。

缺点(痛点):

  1. 系统生态支持不一:Android 对 PWA 支持完美;但 iOS (Safari) 对 PWA 支持一直比较消极(比如曾长期不支持 Web Push 消息推送,直到 iOS 16.4 才逐步开放),这极大地限制了 PWA 的普及。
  2. 硬件访问受限:无法像 RN/Flutter 那样调用底层的蓝牙、NFC、高级相机特性等。
  3. 性能上限:依然是跑在浏览器内核里,渲染复杂动画和长列表的性能依然不如 RN 等跨端方案。