想象一下,你是一位户外旅行者。以前,你要去一座深山露营,必须提前下载离线地图、打包所有装备、检查天气预警,稍有不慎就会在山里“失联”。现在,你只需要打开一个网页,它就能像一位贴身向导一样:帮你把地图悄悄缓存到手机里(离线可用)、把图标放到手机桌面(安装到主屏)、在天气突变时主动提醒你(消息推送)、甚至在信号不好的时候帮你把照片排队上传(后台同步)。这个神通广大的网页,就是 PWA(渐进式 Web 应用)。 PWA 不是一个单一的技术,而是一整套让网页“进化”成原生应用的装备包。它就像把一座临时帐篷升级成了一座可移动的坚固营地:Service Worker 是营地总管、Manifest 是营地蓝图、Cache API 是物资仓库、Push API 是信鸽、Background Sync 是物流车队、Lighthouse 是质检员。本篇文章将用“搭营地”这个比喻,带你零基础看懂 PWA 的每一个核心部件。

岗位关联

掌握 PWA 后,你将能独立完成“网页离线化 + 安装到桌面 + 消息触达”这条完整的原生体验链路,这在电商、资讯、工具类 Web 项目中是极具含金量的加分技能,也是前端工程化能力的重要分水岭。

目录
  1. 可视化演示
  2. 认识PWA本质
  3. 营地蓝图Manifest
  4. 营地总管SW
  5. 物资仓库缓存
  6. 信鸽与车队
  7. 营地质检员Lighthouse
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

本节配有交互动效(进入视口后自动加载,约 3KB)。

PWA核心能力全景演示 Service Worker 在后台独立线程运行,拦截网络请求并管理缓存,实现离线访问能力 Cache API 存储请求-响应对,配合缓存策略(Cache First/Network First)优化加载速度 Push API 允许服务器在应用未打开时推送消息,通过推送服务触达用户 缓存命中率 = 缓存命中数 / 请求总数 × 100% Service Worker 是PWA离线与推送能力的核心基础设施

认识PWA本质

想象一下,你家附近有两家餐厅:一家是每天早晨支起棚子的临时摊位,营业时间短、刮风下雨就歇业、没法提前订位;另一家是固定的餐厅,有招牌、有菜单、能预约、雨天照样营业。传统网页就像临时摊位,而原生 App 就像固定餐厅。PWA 的野心,就是让网页这个“临时摊位”升级成“固定餐厅”——不需要下载、不需要安装,却拥有餐厅的全部体验。

从技术上讲,PWA 的全称是 Progressive Web App,即“渐进式 Web 应用”。“渐进”的含义是:它不要求你一次性拥有全部原生能力,而是根据浏览器的支持情况逐步增强。你能离线访问就享受离线,你能推送就收获推送,一切以“能用就行”为底线,以“体验更好”为目标。

PWA 的三大核心能力可以概括为:可靠(Reliable,离线也能打开)、快速(Fast,秒开页面)、沉浸(Engaging,像 App 一样可安装、可推送)。这三大能力分别由 Service Worker、Cache API、Web App Manifest、Push API 等技术支撑。相比传统网页,PWA 多了离线能力和安装入口;相比原生 App,PWA 无需应用商店审核、无需下载安装包、天然跨平台。

回到餐厅的比喻:可靠性是餐厅的“雨天也营业”,快速是“点菜即上菜”,沉浸是“熟客刷脸进门”。PWA 的每一件装备,都是为了把这三个维度做到极致。

PWA=可靠+快速+沉浸\text{PWA}=\text{可靠} + \text{快速} + \text{沉浸}
这个公式是 PWA 的“总纲”:可靠对应离线能力,快速对应缓存性能,沉浸对应安装与推送。
  1. 可靠(Reliable)指网络不稳定甚至断网时,页面依然能打开并展示内容,这是 PWA 最核心的价值。
    可靠↔离线可用\text{可靠} \leftrightarrow \text{离线可用}
  2. 快速(Fast)指页面加载速度极快,接近原生 App 的启动体验,通常通过缓存实现。
    快速↔缓存命中率\text{快速} \leftrightarrow \text{缓存命中率}
  3. 沉浸(Engaging)指用户像使用 App 一样使用网页,包括桌面图标、全屏启动、消息推送。
    沉浸↔安装+推送\text{沉浸} \leftrightarrow \text{安装} + \text{推送}

营地蓝图Manifest

建营地之前,你需要先画一张蓝图:营地叫什么名字、挂什么招牌、刷什么颜色的漆、开门时展示什么欢迎画面。Web App Manifest 就是 PWA 的这张蓝图。它是一份 JSON 文件,告诉浏览器“我的应用叫什么、图标长什么样、主题色是什么、启动时全屏还是带地址栏”。

在代码层面,你需要在 HTML 的 <head> 中通过 <link rel="manifest" href="manifest.json"> 引入这份蓝图。Manifest 中最常用的字段包括:name(应用全名)、short_name(桌面显示短名)、icons(应用图标,通常提供 192px 和 512px 两种尺寸)、start_url(启动时打开的页面)、display(以 standalone 方式独立显示)、theme_color(顶栏主题色)和 background_color(启动画面背景色)。

当这份 Manifest 配置达标后,浏览器就会在地址栏或菜单中提示“安装应用”。在桌面端,用户可以将 PWA “安装”到桌面,生成一个图标;在移动端,可以像 App 一样添加到主屏幕。启动时,PWA 会以独立窗口展示,并显示配置好的启动画面,完全告别浏览器的地址栏,让用户产生“这就是一个 App”的错觉。

蓝图画好了,营地就有了“脸面”。但这张蓝图只能解决“看起来像 App”,要让营地真正运作起来,还需要一位能力超群的总管——Service Worker。

Manifest={name,icons,start_url,display,theme_color}\text{Manifest}=\{\text{name},\text{icons},\text{start\_url},\text{display},\text{theme\_color}\}
这五件套是让网页“看起来像个 App”的最小配置集,缺一不可。
  1. name(营地名称)应用全名,用于应用安装提示和启动画面展示,对应营地的大招牌。
    name="奇妙露营"\text{name} = \text{"奇妙露营"}
  2. icons(营地招牌图标)192px 和 512px 尺寸的图标,分别用于桌面图标和启动画面。
    icons∈{192px,512px}\text{icons} \in \{\text{192px}, \text{512px}\}
  3. start_url(营地入口)用户点击图标后打开的页面地址,对应营地大门的位置。
    start_url="/index.html"\text{start\_url} = \text{"/index.html"}
  4. display(营地展现形式)standalone 表示独立窗口运行,隐藏浏览器地址栏,对应“无棚全景观景台”。
    display=standalone\text{display} = \text{standalone}
  5. theme_color(营地主题色)浏览器顶栏和任务切换器的颜色,对应营地统一的装饰色系。
    theme_color="#818CF8"\text{theme\_color} = \text{"\#818CF8"}

营地总管SW

一个营地要想二十四小时运转,需要一位总管。这位总管不睡觉、不下班、站在营地门口,替你把所有物资(请求)分类处理:有库存的走仓库(缓存),没库存的去采购(网络),采购失败的给备用方案。Service Worker(简称 SW)就是这位总管。它是浏览器在后台独立运行的一段 JavaScript 脚本,独立于页面、不依赖页面存在,即使页面关闭,它依然值守。

要请来这位总管,需要三步。第一步注册:在页面中调用 navigator.serviceWorker.register('/sw.js'),告诉浏览器“我要聘请一位总管”;第二步安装:浏览器下载 sw.js 脚本,SW 触发 install 事件,通常在这里预缓存静态资源;第三步激活:安装成功后 SW 触发 activate 事件,清理旧缓存并完全掌控页面,此时页面刷新后的所有请求都会经过 SW。

SW 最强大的能力是拦截请求。通过监听 fetch 事件,SW 可以决定每个请求是走缓存、走网络、还是走“缓存优先、网络兜底”的混合策略。这就是 PWA 离线的核心——不是浏览器“自动缓存”,而是 SW 这位总管在替你精准地管理每一次物资调度。

总管已经到位,接下来就是搭建仓库。仓库里放什么货、什么时候补货、什么策略出货,就对应了 PWA 的缓存策略。

SW 生命周期: 注册→安装→激活→拦截请求\text{SW 生命周期}:\ \text{注册} \rightarrow \text{安装} \rightarrow \text{激活} \rightarrow \text{拦截请求}
SW 生命周期是 PWA 离线能力的心脏起搏器,理解它等于掌握了离线缓存的开关。
  1. 注册(聘请总管)页面加载后调用 register(),传入 SW 脚本路径,浏览器开始下载脚本。
    register(sw.js)\text{register}(\text{sw.js})
  2. 安装(总管培训)SW 首次下载成功触发 install 事件,此时将核心资源写入缓存,相当于总管熟悉仓库物资。
    install→cache.addAll(资源列表)\text{install} \rightarrow \text{cache.addAll}(\text{资源列表})
  3. 激活(总管上岗)安装完成触发 activate 事件,清理失效的旧缓存,并接管所有页面请求。
    activate→caches.delete(旧缓存)\text{activate} \rightarrow \text{caches.delete}(\text{旧缓存})
  4. 拦截请求(日常调度)页面发起 fetch 请求时,SW 监听并决定响应来源,相当于总管决定物资从仓库出还是现采购。
    fetch→策略选择(缓存/网络)\text{fetch} \rightarrow \text{策略选择}(\text{缓存/网络})

物资仓库缓存

营地总管需要一个仓库来存放物资,Cache API 就是 PWA 的物资仓库。它是一个存储网络响应(Request/Response)的缓存系统,支持在 SW 中通过 caches.open() 打开仓库、cache.put() 存放物资、cache.match() 取出物资。

仓库管理的关键是出货策略——当用户请求一个资源时,是先看仓库(缓存)还是先看市场(网络)?常用的策略有两种。第一种是缓存优先(Cache First):请求到达时先查缓存,命中就直接返回,没命中再去网络获取并顺手存入仓库。这种策略适合图片、CSS、JS 等不常变的静态资源,速度最快。第二种是网络优先(Network First):请求先走网络,网络成功就用网络数据并更新缓存,失败才回退到缓存。这种策略适合 API 数据、文章内容等实时性要求高的资源。

除了存取和策略,仓库还需要定期清理。缓存不是越多越好,当 SW 新版本激活时,activate 事件中通常会删除旧版本的缓存仓库,防止磁盘空间膨胀。这就是为什么 PWA 的资源文件通常带版本号(如 app-v1.js),便于精确控制哪些缓存可以回收。

仓库已备齐物资,营地可以做到“自给自足”了。但一个只靠用户主动访问的营地,还算不上真正的原生体验——你能主动走进营地,但营地为什么不能主动联系你呢?

Cache 策略: Cache First 或 Network First\text{Cache 策略}:\ \text{Cache First} \ \text{或} \ \text{Network First}
选择哪种策略取决于资源类型:静态资源用 Cache First,动态数据用 Network First,这是缓存的黄金法则。
  1. 打开仓库通过 caches.open('my-cache-v1') 打开指定名称的缓存仓库,相当于推开仓库大门。
    caches.open(name)→Promise<Cache>\text{caches.open}(\text{name}) \rightarrow \text{Promise}<\text{Cache}>
  2. 预缓存(提前备货)在 SW 的 install 事件中,用 cache.addAll() 将核心资源批量存入仓库,完成“开业备货”。
    cache.addAll([’/index.html’,’/app.js’])\text{cache.addAll}([\text{'/index.html'}, \text{'/app.js'}])
  3. 缓存优先出库event.respondWith 中先 caches.match(event.request),命中则返回缓存;未命中则 fetch(event.request) 并存入缓存。
    cache.match(req)→Response∣undefined\text{cache.match}(\text{req}) \rightarrow \text{Response} \mid \text{undefined}
  4. 动态缓存入库网络请求成功后,将请求和响应配对存入缓存,以备后续离线使用。
    cache.put(req,res)\text{cache.put}(\text{req}, \text{res})

信鸽与车队

一个成熟的营地需要主动联系外界:天气变了放出信鸽提醒你加衣、你寄出的信件由车队在信号恢复时统一送达。Push API 是信鸽,Notification API 是鸽哨,Background Sync 是物流车队。

Push API 允许服务器主动向用户推送消息,即使网页处于关闭状态。流程是:SW 中通过 PushManager.subscribe() 向推送服务订阅,获得一个唯一的订阅标识;服务器拿着这个标识即可随时发送推送消息;当消息到达时,SW 的 push 事件被触发,再通过 Notification API 展示通知。这就好比你给了营地一只信鸽,营地任何时候都能飞鸽传书给你。

Notification API 用于展示系统级通知。在 SW 中监听 push 事件后,调用 self.registration.showNotification(title, options) 即可弹出通知,包含标题、正文、图标、点击跳转链接等。请注意,Notification 需要用户在页面中主动授权,这对应“你同意营地给你传信”。

Background Sync 解决的是“网络不好时的数据提交”。当用户离线时填写了一个表单,网页无法提交;Background Sync 会把这次提交“排队寄存”,等网络恢复后自动重试。原理是 SW 注册一个 sync 事件,浏览器在检测到网络连通时触发该事件,SW 在事件中完成数据同步。这在弱网环境(地铁、电梯、山区)中极大提升了表单提交的成功率。

信鸽与车队到齐,营地已经能主动联系用户、离线提交数据。但这一切配置是否合格?这需要一位独立的质检员来验收打分。

Push+Notification+Background Sync=主动触达+离线提交\text{Push} + \text{Notification} + \text{Background Sync} = \text{主动触达} + \text{离线提交}
推送解决了“营地主动找你”,同步解决了“你离线也不能错过”,二者合璧让 PWA 粘性大增。
  1. 订阅推送(驯养信鸽)页面调用 pushManager.subscribe() 获取订阅对象,并将其发送给服务器保存。
    pushManager.subscribe(options)→PushSubscription\text{pushManager.subscribe}(\text{options}) \rightarrow \text{PushSubscription}
  2. 服务器推送(放飞信鸽)服务器通过 Web Push 协议将消息发送到用户的浏览器推送服务,无需用户在线。
    服务器→Web Push浏览器推送服务\text{服务器} \xrightarrow{\text{Web Push}} \text{浏览器推送服务}
  3. 监听并展示(鸽哨响起)SW 监听 push 事件,通过 showNotification() 展示系统通知。
    self.addEventListener(’push’,handler)\text{self.addEventListener}(\text{'push'}, \text{handler})
  4. 注册后台同步(物流排队)页面中通过 registration.sync.register('sync-form') 注册一个后台同步任务。
    registration.sync.register(tag)\text{registration.sync.register}(\text{tag})
  5. 同步任务执行(车队发车)网络恢复后 SW 的 sync 事件被触发,在处理器中完成数据提交。
    self.addEventListener(’sync’,handler)\text{self.addEventListener}(\text{'sync'}, \text{handler})

营地质检员Lighthouse

营地建成后,你需要一位质检员从头到尾检查一遍:蓝图是否清晰、总管是否尽责、仓库是否高效、信鸽是否畅通。Lighthouse 就是 PWA 界的质检员。它是谷歌开源的一款自动化审计工具,可以对网页进行全方位体检并打分。

Lighthouse 的 PWA 审计项涵盖多个维度。以 Chrome 浏览器为例,打开 DevTools → Lighthouse → 勾选 PWA → Generate Report,就能得到一份 PWA 评分报告。核心检查项包括:是否注册了 Service Worker、Manifest 是否配置完整、是否支持离线访问、启动画面是否配置、图标尺寸是否达标、页面是否使用 HTTPS。每一项都有对应的权重,最终生成 0-100 的 PWA 得分。

得分之外,Lighthouse 还会给出最佳实践和性能建议,比如:资源是否启用缓存、图片是否压缩、第三方脚本是否拖慢加载。对开发者而言,Lighthouse 的意义在于“可量化的质量标准”——你不需要靠感觉判断 PWA 做得好不好,一份报告就告诉你哪里扣分、为什么扣分、怎么修复。

至此,六大部件全部到位:认识本质、Manifest 蓝图、SW 总管、缓存仓库、推送同步、Lighthouse 质检。从一座临时帐篷到一座移动营地,PWA 用一套渐进增强的装备,真正让网页拥有了原生的体验。你需要做的,就是拿起这份营建手册,亲自动手搭一座自己的营地。

PWA Score=f(SW,Manifest,HTTPS,Offline)\text{PWA Score} = f(\text{SW}, \text{Manifest}, \text{HTTPS}, \text{Offline})
Lighthouse 告诉你“营地哪里漏风”,修复每一项扣分点,就是一步步把营地建得更坚固。
  1. SW 注册检查(总管是否在岗)Lighthouse 检查页面是否成功注册 Service Worker 并正常工作。
    ScoreSW=注册成功 ? 1:0\text{Score}_{\text{SW}} = \text{注册成功} \ ? \ 1 : 0
  2. Manifest 配置检查(蓝图是否齐全)检查 name、icons、start_url、display 等关键字段是否完整。
    ScoreManifest=配置达标字段数总字段数\text{Score}_{\text{Manifest}} = \frac{\text{配置达标字段数}}{\text{总字段数}}
  3. 离线可用检查(断网试验)Lighthouse 会模拟断网,验证页面能否从缓存正常加载。
    ScoreOffline=离线加载成功 ? 1:0\text{Score}_{\text{Offline}} = \text{离线加载成功} \ ? \ 1 : 0
  4. HTTPS 检查(营地安保系统)PWA 必须在 HTTPS 环境下运行,SW 和 Push API 都要求安全上下文。
    HTTPS=安全上下文⇒SW 可用\text{HTTPS} = \text{安全上下文} \Rightarrow \text{SW 可用}

AI 应用场景

电商网站的离线购物车

阶段:前端工程化与用户体验优化

解决的问题:电商用户在弱网或断网场景下打开商品页会白屏、加入购物车操作失败。借助 PWA 的 Service Worker 和 Cache API,商品页静态资源被预缓存,浏览和加购操作在离线时也能正常完成;Background Sync 在恢复网络后自动同步购物车数据,显著降低弱网场景下的流失率。

具体案例:某跨境电商平台接入 PWA 后,对东南亚弱网用户进行 A/B 测试,发现首屏加载时间从 8 秒降至 2.5 秒,离线环境下将商品加入购物车的成功率提升至 98%,整体转化率提升 12%。

新闻资讯应用的消息推送

阶段:用户触达与留存优化

解决的问题:传统新闻网页无法主动触达用户,用户关闭浏览器后即“失联”。通过 Push API 和 Notification API,新闻站点可以在重大新闻发生时主动推送通知,即使网页完全关闭也能送达,将用户重新拉回页面。

具体案例:某新闻门户接入 PWA 推送后,通过向已授权的 10 万用户推送突发新闻通知,推送点击率达到 18%,用户 30 日留存率提升了 6 个百分点,显著改善了对搜索引擎入口的依赖。

订餐系统的离线菜单预订

阶段:业务连续性保障

解决的问题:用户在电梯、地铁等人群密集场景下信号差,无法加载菜单或提交订单。借助 PWA 的 Cache First 策略缓存菜单资源和 Network First 策略处理订单 API,配合 Background Sync 在弱网下排队提交订单,确保订餐业务不因网络波动而中断。

具体案例:某餐饮 SaaS 为商户提供的 H5 点餐页面接入 PWA 后,离线状态下的菜单展示成功率从 0% 提升到 100%,订单提交失败率从 15% 降至 2%,商户满意度大幅提升。

Python 演示

python87 行
# -*- coding: utf-8 -*-"""PWA 缓存策略模拟器:模拟 Service Worker 的缓存优先调度"""import timefrom collections import OrderedDict  class CacheStore:    """模拟 Cache API 的仓库"""     def __init__(self, name):        self.name = name        self._store = OrderedDict()     def put(self, url, content):        self._store[url] = content     def match(self, url):        return self._store.get(url)     def size(self):        return len(self._store)  class ServiceWorkerSimulator:    """模拟 Service Worker 的请求调度器"""     def __init__(self):        self.cache = CacheStore("my-site-v1")        self.network_hit = 0        self.cache_hit = 0        self._miss = 0     def install(self, assets):        """install 阶段:预缓存核心资源"""        for url, content in assets.items():            self.cache.put(url, content)     def _fetch_from_network(self, url):        """模拟网络请求,带固定延迟"""        time.sleep(0.01)        return "<html>网络响应: " + url + "</html>"     def fetch(self, url):        """fetch 事件:缓存优先,未命中走网络并回填缓存"""        cached = self.cache.match(url)        if cached is not None:            self.cache_hit += 1            return cached        response = self._fetch_from_network(url)        self.cache.put(url, response)        self.network_hit += 1        return response     def stats(self):        total = self.cache_hit + self.network_hit        hit_rate = (self.cache_hit / total * 100) if total else 0.0        return {            "总请求数": total,            "缓存命中": self.cache_hit,            "网络请求": self.network_hit,            "命中率": "%.1f%%" % hit_rate,            "仓库容量": self.cache.size(),        }  if __name__ == "__main__":    # 安装 SW:预缓存核心资源    sw = ServiceWorkerSimulator()    core_assets = {        "/index.html": "<html>首页(本地缓存版)</html>",        "/app.js": "console.log('app js')",        "/style.css": "body { color: #818CF8; }",    }    sw.install(core_assets)     # 模拟 10 次页面资源请求    request_urls = [        "/index.html", "/app.js", "/style.css",        "/api/products", "/index.html", "/api/products",        "/app.js", "/style.css", "/index.html", "/api/user/profile",    ]    for url in request_urls:        sw.fetch(url)     print("=== PWA 缓存策略模拟结果 ===")    for key, value in sw.stats().items():        print(f"{key}: {value}")
目的:这个类比例子演示了 Service Worker 的核心调度逻辑——预缓存核心资源、缓存优先响应、网络回填缓存。运行后可以看到频繁访问的资源命中率越来越高,这正是 PWA 离线的本质。
  1. self.cache = CacheStore("my-site-v1")创建一个 Cache API 风格的仓库,对应浏览器中的 caches.open('my-site-v1')。
  2. def install(self, assets)模拟 SW 的 install 事件,预缓存页面骨架、JS、CSS 等核心资源“备货”。
  3. self.cache_hit += 1每命中一次缓存记一次数,对应浏览器性能面板中的"disk cache hit"。
  4. response = self._fetch_from_network(url)缓存未命中时回退到网络请求,这就是 Cache First 策略的“兜底”。
  5. hit_rate = (self.cache_hit / total * 100)计算缓存命中率,Lighthouse 报告中同样关注这个指标来判断缓存策略是否合理。

常见误区

误区我以为 PWA 就是把网页代码打包成一个 APK 或 IPA 安装包,跟用 Cordova 之类的壳子包一层一样,本质上是“套壳应用”。

正解PWA 和套壳完全不是一个物种。套壳是“给网页穿了件 App 的外衣”,内在还是网页,而且依赖浏览器内核;PWA 是“让网页学会了 App 的本领”,不需要打包、不需要上架商店,直接在浏览器中通过标准 Web API(SW、Manifest、Push)获得原生能力。PWA 是浏览器原生支持的,而不是通过壳去模拟,这是本质区别。

为何常见:人容易从“产物形态”推断技术本质——看到 PWA 也能出现在桌面、也能全屏运行,就自然联想到“那不就是打包了个壳吗”。其实 PWA 从头到尾都是网页,只是“进化”了。

误区很多教程说“注册 SW 后,页面断网也能访问”,我就以为只要调用了 `register()`,所有页面资源都会被神奇地缓存,断网自动能用。

正解注册 SW 只是“聘请了总管”,还需要在 install 事件中通过 cache.addAll() 预缓存资源,并在 fetch 事件中编写缓存策略,页面才能真正离线可用。注册只完成了生命周期第一步,没有预缓存和拦截逻辑的 SW 只是一个“光杆司令”。这也解释了为什么离线能力的核心在于“缓存策略的编写”,而非“注册动作本身”。

为何常见:因为“注册 SW”是唯一一个在页面上可见的代码片段,容易误以为它是离线能力的全部。而 install/activate/fetch 事件里的逻辑分布在不同文件中,容易忽略。

误区我一开始做 PWA,恨不得把所有请求都缓存下来,总觉得“缓存了总比不缓存好”,结果用户反馈页面数据总是旧的。

正解缓存是双刃剑。静态资源缓存了提升速度,但动态数据(如用户余额、订单状态、评论区)如果用 Cache First 策略,用户永远看到旧数据。正确的做法是区分资源类型:静态资源用 Cache First,API 和用户相关数据用 Network First 或 Network Only。同时,缓存要有版本管理和定期清理机制,否则旧缓存无限膨胀,反而拖慢应用。

为何常见:人容易用直觉理解缓存——“多存点东西总没错”。但缓存的核心矛盾就是“速度 vs 新鲜度”,是否缓存、用哪种策略取决于资源是否多变。

误区看了一些宣传文章说 PWA 能推送、能安装、能离线,就觉得“那原生 App 还有什么存在价值”,PWA 很快会终结 App 时代。

正解PWA 能覆盖大量高频轻量场景,但受限于 Web 平台的沙箱能力,它在调用系统硬件(摄像头深度、蓝牙、NFC、文件系统)、复杂动画性能、后台常驻任务等方面仍无法与原生 App 匹敌。Capabilities Project 虽然不断缩小差距,但 PWA 更适合做“轻量触达”的补充,而非“完全替代”。Google 的官方定位一直是“PWA 和原生 App 互补共生”。

为何常见:技术文章喜欢用“颠覆”“替代”等吸睛字眼,读者容易只看结论不看限制条件,忽略了 PWA 在系统能力上的边界。

误区我们团队的 PWA 在 Lighthouse 上打了一百分,我就觉得这应用“无敌了”,直到用户还是说“打开慢、卡顿、找不到离线内容”。

正解Lighthouse 的 PWA 分数只代表“PWA 标准的合规度”,检查的是 SW 是否注册、Manifest 是否齐全、是否 HTTPS 等结构性指标,它并不能代表真实用户体验。性能分和用户体验还受到服务器响应速度、网络状况、设备性能、页面复杂度等多重因素影响。Lighthouse 是“底线质检”,不是“体验保证”。一份 100 分的报告只能说明你的营地“结构合规”,但里面住得舒不舒服是另一回事。

为何常见:分数化指标给人带来确定感,“100 分”这个数字天然让人产生“已经做到最好”的错觉,忽略了指标本身的局限。

面试题

Q1. PWA 的“渐进式”最核心的含义是什么?

单选题 · easy

  • A.页面内容逐步加载
  • B.根据浏览器能力逐步增强← 正确答案
  • C.网速提升后逐步解锁功能
  • D.用户逐步学习使用应用

答案:B。“渐进增强”指 PWA 根据浏览器支持情况逐步叠加原生能力:支持的浏览器获得离线、推送等完整体验,不支持的浏览器仍然能作为普通网页访问。A 是资源加载概念,C/D 与浏览器能力无关。

Q2. Web App Manifest 文件采用的格式是什么?

单选题 · easy

  • A.XML
  • B.YAML
  • C.JSON← 正确答案
  • D.INI

答案:C。Web App Manifest 采用 JSON 格式描述应用元数据,如名称、图标、主题色、启动 URL 等。类比营地的蓝图用统一的标准图纸格式来画,JSON 就是 Web 世界的标准图纸。

Q3. Service Worker 生命周期正确顺序是?

单选题 · mid

  • A.注册→激活→安装
  • B.安装→注册→激活
  • C.注册→安装→激活← 正确答案
  • D.激活→安装→注册

答案:C。先注册(register())下载脚本,再触发 install 事件(预缓存资源),最后触发 activate 事件(清理旧缓存、接管页面)。类比营地先聘请总管→培训熟悉物资→正式上岗。

Q4. 下列资源最适合使用 Cache First(缓存优先)策略的是?

单选题 · mid

  • A.用户余额接口
  • B.商品库存数量接口
  • C.应用 Logo 图片← 正确答案
  • D.实时天气接口

答案:C。Cache First 适合不常变的静态资源,Logo 图片是最典型代表。余额、库存、天气都是实时性高的动态数据,若用缓存优先会让用户看到过期信息,应改用 Network First。

Q5. Push API 推送消息到达后,由谁负责展示通知?

单选题 · mid

  • A.服务器
  • B.浏览器页面 JS
  • C.Service Worker← 正确答案
  • D.桌面操作系统

答案:C。推送消息到达后触发 SW 的 push 事件,在 SW 中调用 showNotification() 展示系统通知。即使页面关闭 SW 也在后台运行,这正是 PWA 能“关闭后仍收到推送”的原因。B 的页面 JS 在页面关闭后无法运行。

Q6. Background Sync 解决的问题是?

单选题 · easy

  • A.页面加载速度慢
  • B.网络恢复后自动重试数据提交← 正确答案
  • C.服务器负载过高
  • D.图片显示模糊

答案:B。Background Sync 在网络不可用时将数据提交任务排队,网络恢复后自动重试。类比物流车队在道路畅通后统一发车,确保用户的离线操作不丢失。

Q7. 下列哪些条件是 PWA 安装到桌面所必需的?(多选)

多选题 · mid

  • A.配置完整的 Manifest← 正确答案
  • B.注册 Service Worker← 正确答案
  • C.使用 HTTPS← 正确答案
  • D.页面使用 Vue 框架

答案:ABC。PWA 可安装条件包括:完整的 Manifest(名称图标配置)、已注册且可用的 Service Worker、HTTPS 安全上下文。框架选择与可安装性无关,Vue、React 均可实现 PWA。

Q8. 关于 Service Worker,下列说法正确的是?(多选)

多选题 · hard

  • A.SW 是独立于页面的 JS 线程← 正确答案
  • B.SW 可以访问 DOM
  • C.页面关闭后 SW 仍然运行← 正确答案
  • D.SW 只能运行在 HTTP 协议下

答案:AC。SW 是独立线程且页面关闭后仍运行,这两点构成其拦截请求和推送的基础。SW 属于 Worker 环境,没有 window 对象无法操作 DOM。它要求 HTTPS 安全上下文,所以 D 错误。

Q9. 当 Service Worker 新版本激活时,通常在哪里清理旧缓存?

单选题 · hard

  • A.注册阶段
  • B.install 事件
  • C.activate 事件← 正确答案
  • D.fetch 事件

答案:C。activate 事件在新版 SW 接管前触发,是清理旧版本缓存的正确时机。类比新总管上任时,趁交接期盘点并清理过期的仓库物资,避免磁盘空间膨胀。

Q10. 关于 PWA 与原生 App 的对比,正确的有?(多选)

多选题 · hard

  • A.PWA 无需应用商店审核← 正确答案
  • B.PWA 可以完全替代原生 App
  • C.PWA 支持跨平台运行← 正确答案
  • D.PWA 无法调用任何系统硬件

答案:AC。PWA 通过 URL 访问无需商店审核,且天然跨平台(同一套代码运行于 Win/macOS/Android/iOS)。但它受 Web 沙箱限制,无法在复杂硬件场景下替代原生 App,D 说法绝对化——部分硬件接口(摄像头、地理位置等)已可用。