PWA渐进式Web应用技术深度解析

一、概述

1.1什么是PWA

渐进式Web应用(Progressive Web App) 是一种使用现代Web技术构建的应用程序,它结合了Web应用的广泛覆盖性和原生应用的优秀体验。核心特性包括:

  • 渐进增强:在所有浏览器中运行,在支持新特性的环境中提供增强体验
  • 网络韧性:通过Service Worker实现离线工作和弱网环境下的无缝体验,跟客户端应用体验类似
  • 类原生体验:可安装到设备主屏,支持全屏运行、推送通知和硬件访问
  • 应用化分发:通过URL直接访问,也可通过应用商店分发,可以安装到桌面图标作为独立应用打开

1.2PWA演进路线

timeline title PWA技术发展里程碑 2015年 : Google正式提出PWA概念 2016年 : Service Worker成为W3C草案 2018年 : Web App Manifest标准化 2020年 : 全球浏览器支持率>90% 2023年 : 高级系统能力落地

二、对比分析

2.1技术特性对比矩阵

特性 传统Web应用 原生应用 PWA
安装要求 无需安装 必须安装 可选安装
离线功能 不支持 支持 支持
更新机制 实时更新 应用商店审核 后台静默更新
分发方式 URL直达 应用商店 URL+应用商店
跨平台成本 极低 高(多平台开发) 低(单一代码库)
设备能力访问 有限 完整 逐渐完善
存储空间占用 一般<5MB 50-500MB 10-50MB

2.2商业价值对比

指标 传统Web PWA 提升幅度
用户参与度 1.8% 4.2% +133%
转化率 1.1% 3.5% +218%
加载时间(3G网络) 5.2s 1.3s -75%
二次访问率 28% 62% +121%

数据来源:Google案例研究(2023)

三、PWA技术特性

3.1技术特性

PWA是依赖浏览器引擎的支持的,当我们打开一个PWA应用,需要一个浏览器引擎负责在后台管理和运行它,但是我们不再使用浏览器的UI页面比如后退/前进按钮等 UI 功能和页面的标题。

除了网页开发基础的html,JavaScript,CSSd等技术体系外,开发一个pwa应用还需要:

  • 一个 web 应用程序清单文件,至少提供浏览器需要的信息来安装 PWA,如应用程序名称和图标。
  • 一个 service worker,至少提供基本的脱机体验。

单页应用可以提供更接近平台特定应用程序的用户体验,所以 PWA 通常以单页应用实现,但是这不是必须的。

3.2 PWA应用架构模型

graph TD A[传统Web技术] --> C[基础设施层] subgraph A[用户界面层] subgraph A1 [UI界面] A11[现代前端框架] end subgraph B[业务逻辑层] B1[状态管理] B2[路由控制] B3[API通信] end end subgraph C[基础设施层] C1[Service Worker] C2[Cache API] C3[IndexedDB] C4[Push API] C5[Web App Manifest] end

注意:数据存储不能用localStorage,但是可以使用IndexDB。

3.3应用程序清单

应用程序清单是PWA应用的名片,也是应用配置

json 复制代码
    {
      "lang": "en",
      "dir": "ltr",
      "name": "Super Racer 3000",
      "name_localized": {
        "de":    "Farbwähler",
        "en":    {"value": "Color Picker"},
        "en-GB": {"value": "Colour Picker", "dir": "ltr"},
        "fr":    {"value": "Sélecteur de Couleur", "lang": "fr-CA", "dir": "ltr"},
        "ar":    {"value": "منتقي الألوان", "dir": "rtl"}
      }
      "short_name": "Racer3K",
      "icons": [{
        "src": "icon/lowres.webp",
        "sizes": "64x64",
        "type": "image/webp"
      }, {
        "src": "icon/lowres.png",
        "sizes": "64x64"
      }, {
        "src": "icon/hd_hi",
        "sizes": "128x128"
      }],
      "scope": "/",
      "id": "superracer",
      "start_url": "/start.html",
      "display": "fullscreen",
      "orientation": "landscape",
      "theme_color": "aliceblue",
      "background_color": "red"
    }
  • background_color 背景色设 阿达d'v
  • dir 设置文本的默认方向
  • display 展示方式配置,fullscreen,standalone,minimao-ui,brower
  • icons 应用图标
  • identity PWA的唯一标识,即使是同一个域名下,不同id也表示不同的应用
  • lang 设置默认语言
  • name 应用名称
  • orientation 屏幕方向
  • scope 作用域,域名下哪些文档是pwa的内容
  • short_name 应用简称
  • shortcuts 快捷方式图标配置,可以创建多个指向不同的页面
  • start_url 启动的首页地址
  • theme_color 主题色设置

3.4 Service Worker

Service Worker 是 Progressive Web App(PWA)的核心组件,充当浏览器与网络之间的代理层,主要职责包括:

  1. 网络请求代理与缓存管理

    • 拦截 HTTP 请求,实现动态缓存策略(如 Cache First/Network First)。
    • 管理静态资源(HTML/CSS/JS)及动态数据的缓存,提升加载性能。
  2. 离线功能支持

    • 通过预缓存和运行时缓存,确保应用在弱网或离线状态下仍可访问。
  3. 后台数据同步

    • 在用户无感知时完成数据同步(通过 Background Sync API)。
  4. 推送通知处理

    • 接收服务器推送事件(即使应用未运行),触发通知(基于 Push API)。

关键特性

  • 独立运行环境

    • 拥有独立的 Worker 上下文,与主线程隔离,无法直接操作 DOM。
    • 生命周期与页面无关,可长期驻留(直至被主动终止)。
  • 异步非阻塞设计

    • 基于 Promise 实现异步操作,避免阻塞主线程。

使用 ServiceWorkerContainer.register() 方法首次注册 service worker,注册会执行如下三个步骤:

  1. 下载
  2. 安装 install事件
  3. 激活 active事件

Servcie worker 可以通过 FetchEvent 事件去响应请求。通过使用 FetchEvent.respondWith 方法,你可以任意修改对于这些请求的响应。用于缓存。

下面代码演示如何使用Service Worker做缓存,增强离线或者弱网体验。

javascript 复制代码
// 添加资源到缓存中
    const addResourcesToCache = async (resources) => {
      const cache = await caches.open("v1");
      await cache.addAll(resources);
    };
    // 添加网络请求缓存
    const putInCache = async (request, response) => {
      const cache = await caches.open("v1");
      await cache.put(request, response);
    };

    const cacheFirst = async ({ request, preloadResponsePromise, fallbackUrl }) => {
      // 首先,尝试从缓存中获取资源
      const responseFromCache = await caches.match(request);
      if (responseFromCache) {
        return responseFromCache;
      }

      // 接下来,尝试使用导航预加载的响应
      const preloadResponse = await preloadResponsePromise;
      if (preloadResponse) {
        console.info("using preload response", preloadResponse);
        putInCache(request, preloadResponse.clone());
        return preloadResponse;
      }

      //最后尝试从网络中获取资源
      try {
        const responseFromNetwork = await fetch(request);
        // 响应可能会被使用
        // 我们需要将它的拷贝放入缓存
        // 然后再返回该响应
        putInCache(request, responseFromNetwork.clone());
        return responseFromNetwork;
      } catch (error) {
        const fallbackResponse = await caches.match(fallbackUrl);
        if (fallbackResponse) {
          return fallbackResponse;
        }
        // 当回落的响应也不可用时,
        // 我们便无能为力了,但我们始终需要
        // 返回 Response 对象
        return new Response("Network error happened", {
          status: 408,
          headers: { "Content-Type": "text/plain" },
        });
      }
    };

    // 启用导航预加载
    const enableNavigationPreload = async () => {
      if (self.registration.navigationPreload) {
        await self.registration.navigationPreload.enable();
      }
    };
    // 激活时启用导航预加载,也是一种缓存
    self.addEventListener("activate", (event) => {
      event.waitUntil(enableNavigationPreload());
    });
    // 安装时,将必要的资源缓存起来,相当于安装资源文件到本地。
    self.addEventListener("install", (event) => {
      // waitUntil是一个异步等待,即addResourcesToCache返回的promise resolve之前不会出发installed事件。
      event.waitUntil(
        addResourcesToCache([
          "/",
          "/index.html",
          "/style.css",
          "/app.js",
          "/image-list.js",
          "/star-wars-logo.jpg",
          "/gallery/bountyHunters.jpg",
          "/gallery/myLittleVader.jpg",
          "/gallery/snowTroopers.jpg",
        ]),
      );
    });
    // 请求拦截,优先从缓存中获取,
    self.addEventListener("fetch", (event) => {
      event.respondWith(
        cacheFirst({
          request: event.request,
          preloadResponsePromise: event.preloadResponse,
          fallbackUrl: "/gallery/myLittleVader.jpg",
        }),
      );
    });

导航预加载是什么?

导航预加载是 Service Worker 的一项功能,在 Service Worker 启动的同时,允许浏览器提前并行加载页面资源(如 HTML 主文档),而不是等待 Service Worker 完全初始化后再发起导航请求。

  • 解决 Service Worker 启动延迟问题(约 50~200ms),加速首屏渲染。

3.5 Service Worker更新

通过上面的介绍我们知道了ServiceWorker可以通过静态资源和请求的缓存从而实现离线访问,那么我们的这些资源和请求如何更新呢。

如下几种情况会触发更新:

当下载的文件被发现是更新的脚本文件,就会试图安装

  1. 是在页面或网站遇到的第一个 service worker
  2. 或者新的文件与现有的 service worker 不同(字节对比)

新版本安装后不会马上激活,会处于一个worker in waiting的状态,浏览器默认会等待所有使用旧 Service Worker 的页面关闭后,才激活新版本(避免同时运行两个版本导致冲突)。

下面是一个更新的代码示例

javascript 复制代码
const version = 'v2'
const addResourcesToCache = async (resources) => {
  const cache = await caches.open(version);
  await cache.addAll(resources);
};

// 怎么触发,只要检测到新
self.addEventListener("install", (event) => {
  event.waitUntil(
    addResourcesToCache([
      "/",
      "/index.html",
      "/style.css",
      "/app.js",
      "/image-list.js",
      // ...
      // 包含新版本需要的其他资源...
    ]),
  );
  // 也可以提前激活
  ServiceWorkerGlobalScope.skipWaiting()
});

const deleteCache = async (key) => {
  await caches.delete(key);
};

const deleteOldCaches = async () => {
  const cacheKeepList = ["v2"];
  const keyList = await caches.keys();
  const cachesToDelete = keyList.filter((key) => !cacheKeepList.includes(key));
  await Promise.all(cachesToDelete.map(deleteCache));
};
// 新版本激活时删除所有旧版本的缓存
self.addEventListener("activate", (event) => {
   event.waitUntil(deleteOldCaches());
   //通知用户更新页面 
   event.waitUntil(
    self.clients.matchAll().then(clients => {
      clients.forEach(client => client.postMessage({ type: 'UPDATE_AVAILABLE' }));
    })
  );
});
// 页面代码
navigator.serviceWorker.addEventListener('message', event => {
  if (event.data.type === 'UPDATE_AVAILABLE') {
    showToast('新版本已下载,请刷新页面!');
  }
});

要点

  1. 确保 sw.js 不被缓存(服务器配置 Cache-Control: no-cache)。
  2. 按需使用 skipWaiting(适合单页应用,多页应用需谨慎,可能回到之多个版本同时存在,资源加载冲突)。
  3. 通知用户刷新以应用新版本(提升体验)。

3.6 Cache API高级策略

缓存策略矩阵

策略 实现方式 适用场景 优缺点
缓存优先 优先返回缓存,无则请求网络 静态资源(CSS/JS) 极速加载,可能过期
网络优先 先请求网络,失败则回退缓存 实时数据(API响应) 数据最新,依赖网络
仅缓存 直接返回缓存 离线App 立即响应,无网络交互

代码示例

javascript 复制代码
    // 高级缓存策略实现
    const CACHE_STRATEGIES = {
      STATIC: 'static-v2',
      DYNAMIC: 'dynamic-v2'
    };

    self.addEventListener('fetch', event => {
      const url = new URL(event.request.url);
      
      // 静态资源:缓存优先
      if (isStaticAsset(url)) {
        event.respondWith(
          caches.match(event.request)
            .then(cached => cached || fetchAndCache(event.request, CACHE_STRATEGIES.STATIC))
        );
        return;
      }
      
      // API请求:网络优先
      if (isAPIRequest(url)) {
        event.respondWith(
          fetch(event.request)
            .then(networkResponse => {
              cacheResponse(event.request, networkResponse.clone(), CACHE_STRATEGIES.DYNAMIC);
              return networkResponse;
            })
            .catch(() => caches.match(event.request))
        );
        return;
      }
      
      // 其他请求:直接网络访问
      event.respondWith(fetch(event.request));
    });

    async function fetchAndCache(request, cacheName) {
      const cache = await caches.open(cacheName);
      const response = await fetch(request);
      await cache.put(request, response.clone());
      return response;
    }

四、关键API

4.1 Clients

当前worker管理的所有客户端对象

  • get(id)通过id获取指定的client窗口
  • matchAll() 获取所有的client,可以指定获取条件
  • 一个 options 对象,允许你为匹配操作设置选项。可用选项包括:
  • includeUncontrolled: Boolean --- 如果设置为true, 匹配操作将返回与当前服务工作者共享相同源的所有服务工作者客户端。否则,它仅返回由当前服务工作者控制的服务工作者客户端。默认值为false.
  • type: 设置想要匹配的 clients 类型。可用值包括 window, worker, sharedworker, 和 all. 默认是 all.
  • openWindow(url) 打开一个窗口,返回一个window类型的client
  • claim() 允许一个service worker调用,将自己设置为其scope下所有clients的控制者
javascript 复制代码
    addEventListener("notificationclick", (event) => {
      event.waitUntil(
        (async function () {
          const allClients = await clients.matchAll({
            includeUncontrolled: true,
          });

          let chatClient;

          // Let's see if we already have a chat window open:
          for (const client of allClients) {
            const url = new URL(client.url);

            if (url.pathname == "/chat/") {
              // Excellent, let's use it!
              client.focus();
              chatClient = client;
              break;
            }
          }

          // If we didn't find an existing chat window,
          // open a new one:
          if (!chatClient) {
            chatClient = await clients.openWindow("/chat/");
          }

          // Message the client:
          chatClient.postMessage("New chat messages!");
        })(),
      );
    });

4.2 CacheStorage&Cache

CacheStorage是Cache的缓存器,是一个Cache集合

  1. caches.open('cacheName')
  2. caches.keys() 获取所有缓存的keys
  3. caches.delete(key)
  4. caches.has(key)
  5. caches.match(request,options) 在整个域名下的缓存中查找缓存

Cache接口为缓存request和response对象提供了存储机制,我们通常可以在service worker中使用它做网络请求的缓存。

需要注意的是,Cache不是只在service worker中使用,它是定义在window上的API。

可以理解为key是request,value是response的一个map。

常用API:

  1. cache.match(request,options)
  2. cache.matchAll(request,options)
  3. cache.add(request)
  4. cache.addAll(requests)
  5. cache.put(request,response)
  6. cache.delete(request,options)

4.3 ExtendableEvent

这个对象通常作为install、activate、fetch事件的回调参数,我们通过调用event.waitUntil(Promise)来延迟对应声明周期的完成,

它是FetchEvent和InstallEvent的父类。

4.4 FetchEvent

这个事件对象实在server worke拦截到fetch请求时回调的事件对象,它包含fetch的信息,包括request和响应对象。提供了event.respondWith()方法,允许我们为fetch提供一个响应。

属性:

event.clientId 发起fetch请求的同源客户端的id

event.preloadResponse ?

event.replacesClientId 页面导航期间,正在被替换的客户端的id

event.resultingClientId 页面导航期间,用于替换的客户端的id

event.request 客户端发送的请求

方法:

event.respondWith() 阻止浏览器的默认 fetch 操作,并且由你自己提供一个响应(可以是一个 promise)。

event.waitUntil():延长事件的生命周期。用于通知浏览器延长超出响应回复时间的任务,例如流和缓存。

javascript 复制代码
    self.addEventListener("fetch", (event) => {
      // 不是get方法不处理,因为cache只支持get
      if (event.request.method !== "GET") return;
      // 代理响应
      event.respondWith(
        (async () => {
          // 获取指定的缓存对象
          const cache = await caches.open("dynamic-v1");
          const cachedResponse = await cache.match(event.request);
          // 如果匹配到缓存 
          if (cachedResponse) {
            // 更新缓存
            event.waitUntil(cache.add(event.request));
            // 返回缓存
            return cachedResponse;
          }
          // 没有缓存,使用网络请求获取
          return fetch(event.request);
        })(),
      );
    });

4.5 InstallEvent

这个事件也是继承ExtendableEvent,核心是waitUntil方法,install事件回调的参数对象,他有一个额外的activeWorker属性,返回当前处于激活状态的Service Worker。

javascript 复制代码
    var CACHE_VERSION = 1;
    var CURRENT_CACHES = {
      prefetch: "prefetch-cache-v" + CACHE_VERSION,
    };

    self.addEventListener("install", function (event) {
      var urlsToPrefetch = [
        "./static/pre_fetched.txt",
        "./static/pre_fetched.html",
      ];
      event.waitUntil(
        caches
          .open(CURRENT_CACHES["prefetch"])
          .then(function (cache) {
            cache
              .addAll(
                urlsToPrefetch.map(function (urlToPrefetch) {
                  return new Request(urlToPrefetch, { mode: "no-cors" });
                }),
              )
              .then(function () {
                console.log("All resources have been fetched and cached.");
              });
          })
          .catch(function (error) {
            console.error("Pre-fetching failed:", error);
          }),
      );
    });

4.6 ServiceWorker

ServiceWorker 是 Service Worker 线程在客户端(主线程)的代理对象,通过 navigator.serviceWorker.controllerServiceWorkerRegistration.active 访问。它表示实际运行在 Service Worker 线程中的实例。

核心属性

属性 类型 说明
scriptURL String Service Worker 脚本的完整 URL(如 "https://example.com/sw.js"
state String 当前生命周期状态:- "installing"(安装中)- "installed"/"waiting"(已安装)- "activating"(激活中)- "activated"(已激活)- "redundant"(已废弃)

核心方法

方法 参数 说明
postMessage(message) message: 任意可序列化数据 向 Service Worker 线程发送消息(线程通过 message 事件接收)

关键事件

事件 触发时机 使用场景
statechange 状态变更时(如从 installinginstalled sw.addEventListener('statechange', () => {<br> console.log('状态更新:', sw.state);<br>});<br>
error 线程脚本执行出错时 sw.addEventListener('error', e => {<br> console.error('SW运行错误:', e.error);<br>});<br>

使用实例

javascript 复制代码
    // 获取当前控制页面的 Service Worker
    const sw = navigator.serviceWorker.controller;

    // 监听状态变化
    sw.addEventListener('statechange', () => {
      if (sw.state === 'activated') {
        console.log('Service Worker 已激活!');
      }
    });

    // 向 Service Worker 发送消息
    document.getElementById('send-btn').addEventListener('click', () => {
      sw.postMessage({
        type: 'REFRESH_DATA',
        payload: { force: true }
      });
    });

    // 处理错误
    sw.addEventListener('error', (e) => {
      showAlert(`Service Worker 异常: ${e.message}`);
    });

4.7 ServiceWorkerContainer

ServiceWorkerContainer 是浏览器暴露给客户端的全局对象(通过 navigator.serviceWorker),用于管理 Service Worker 的注册、通信和控制关系。

关键属性

属性 类型 说明
controller ServiceWorker 当前控制页面的 Service Worker 实例(若未激活则为 null
ready Promise<ServiceWorkerRegistration> 始终解析的 Promise:- 当首个 Service Worker 激活时返回其注册对象- 若已激活则立即返回

核心方法

方法 参数 说明
getRegistration(clientURL) clientURL: String 获取特定 URL 作用域下的注册对象(返回 `Promise<ServiceWorkerRegistration undefined>`)
getRegistrations() - 获取当前域名下所有注册对象(返回 Promise<ServiceWorkerRegistration[]>
register(scriptURL, options) scriptURL: SW 脚本路径options: { scope, type } 注册 Service Worker(返回 Promise<ServiceWorkerRegistration>
startMessages() - 显式开启消息接收(默认在页面加载完成后自动开启)

重要事件

事件 触发时机 使用场景
controllerchange 控制页面的 Service Worker 变更时(如新 Worker 接替控制) javascript<br>navigator.serviceWorker.addEventListener(<br> 'controllerchange', <br> () => location.reload() // 刷新页面应用新版本<br>);<br>
message 收到 Service Worker 消息时 javascript<br>navigator.serviceWorker.addEventListener(<br> 'message', <br> e => console.log('来自SW的消息:', e.data)<br>);<br>
messageerror 消息反序列化失败时 navigator.serviceWorker.addEventListener( 'messageerror', () => console.error('消息解析失败'));

使用实例

javascript 复制代码
    // 检查当前控制页面的 Service Worker
    if (navigator.serviceWorker.controller) {
      console.log('当前控制者:', navigator.serviceWorker.controller.scriptURL);
    }

    // 注册 Service Worker
    navigator.serviceWorker.register('/sw.js', { scope: '/app/' })
      .then(registration => {
        console.log('注册成功! 作用域:', registration.scope);
        
        // 监听新版本更新
        registration.addEventListener('updatefound', () => {
          console.log('检测到新版本!');
        });
      });

    // 等待 Service Worker 就绪
    navigator.serviceWorker.ready.then(registration => {
      console.log('Service Worker 已激活:', registration.active);
    });

    // 接收来自 Service Worker 的消息
    navigator.serviceWorker.addEventListener('message', event => {
      if (event.data.type === 'CACHE_UPDATED') {
        showToast('内容已更新,请刷新页面');
      }
    });

    // 主动向 Service Worker 发送消息
    document.getElementById('reload-btn').addEventListener('click', () => {
      navigator.serviceWorker.controller.postMessage('RELOAD_CACHE');
    });

4.8 ServiceWorkerGlobalScope

ServiceWorkerGlobalScope 是 Service Worker 脚本(如 sw.js)的全局执行环境,在 Service Worker 线程中通过 self 访问。所有 Service Worker 逻辑都在此上下文中运行。

关键属性

属性 类型 说明
registration ServiceWorkerRegistration 当前 Service Worker 的注册对象,用于访问作用域/推送管理等
clients Clients 管理受控的客户端页面(如 clients.matchAll() 获取所有页面)
cookieStore CookieStore 异步读写 Cookie 的 API(替代 document.cookie
serviceWorker ServiceWorker 当前 Service Worker 实例(同 self

核心事件

事件 触发时机 使用示例
install 安装阶段 self.addEventListener('install', e => { /* 预缓存资源 */ });
activate 激活阶段 self.addEventListener('activate', e => { /* 清理旧缓存 */ });
fetch 页面发起请求时 self.addEventListener('fetch', e => { e.respondWith(cacheMatch(e.request)) });
message 收到页面消息时 self.addEventListener('message', e => { console.log(e.data) });
push 收到服务器推送 self.addEventListener('push', e => { showNotification('新消息') });
notificationclick 用户点击通知 self.addEventListener('notificationclick', e => { e.waitUntil(openPage()) });
notificationclose 用户关闭通知 self.addEventListener('notificationclose', e => { /* 日志记录 */ });
pushsubscriptionchange 推送订阅失效时 self.addEventListener('pushsubscriptionchange', e => { renewSubscription() });
contentdelete 内容索引项删除时 self.addEventListener('contentdelete', e => { /* 清理相关资源 */ });

核心方法

方法 说明 使用示例
skipWaiting() 强制跳过等待状态(立即激活新 Service Worker) self.addEventListener('install', e => { self.skipWaiting() });
fetch() 发起网络请求(同全局 fetch,用于动态缓存) fetch(request).then(res => cache.put(request, res))

使用实例

javascript 复制代码
    // sw.js
    self.addEventListener('install', e => {
      e.waitUntil(
        caches.open('v1').then(cache => 
          cache.addAll(['/app.js', '/style.css'])
        ).then(() => self.skipWaiting()) // 立即激活
      );
    });

    self.addEventListener('activate', e => {
      e.waitUntil(
        caches.keys().then(keys => 
          Promise.all(keys.filter(k => k !== 'v1').map(k => caches.delete(k))
        ).then(() => self.clients.claim()) // 控制所有客户端
      );
    });

    self.addEventListener('fetch', e => {
      e.respondWith(
        caches.match(e.request).then(res => 
          res || fetch(e.request).then(netRes => {
            const clone = netRes.clone();
            caches.open('v1').then(cache => cache.put(e.request, clone));
            return netRes;
          })
      );
    });

    self.addEventListener('push', e => {
      const data = e.data.json();
      e.waitUntil(
        self.registration.showNotification(data.title, {
          body: data.message,
          icon: '/icon.png'
        })
      );
    });

    self.addEventListener('notificationclick', e => {
      e.notification.close();
      e.waitUntil(self.clients.openWindow('/notifications'));
    });

4.9 ServiceWorkerRegistration

ServiceWorkerRegistration 是客户端注册 Service Worker 时返回的核心对象(通过 navigator.serviceWorker.register() 获取),代表特定作用域(scope)下的 Service Worker 注册信息。

属性说明

属性 类型 说明
active ServiceWorker 激活状态的 Service Worker 实例,控制当前页面
installing ServiceWorker 安装中的 Service Worker 实例(新注册或更新时出现)
waiting ServiceWorker 等待激活的 Service Worker 实例(已安装但未激活)
cookies CookieStoreManager 管理 Service Worker 作用域下的 Cookie(实验性 API)
navigationPreload NavigationPreloadManager 管理导航预加载功能(优化页面加载速度)
pushManager PushManager 处理推送通知订阅的核心 API
scope String 当前注册的 作用域 URL(如 https://example.com/app/
sync SyncManager 后台数据同步 API(如离线数据同步)
updateViaCache String 更新策略:• imports(默认)• allnone

方法说明

方法 参数 说明
getNotifications() options: { tag: String } 获取当前注册作用域下的通知列表(返回 Promise<Notification[]>
showNotification() title: String, options: Object 显示通知(需 notifications 权限)• options 包含图标/振动等配置
unregister() - 注销当前 Service Worker 注册(返回 Promise<Boolean>
update() - 强制检查更新 Service Worker 脚本(绕过 24 小时默认缓存)

事件说明

事件 触发时机 使用示例
updatefound 检测到新版本的 Service Worker 开始安装 registration.addEventListener('updatefound', () => { console.log('新版本开始安装!');});

使用场景

javascript 复制代码
    // 注册 Service Worker
    const registration = await navigator.serviceWorker.register('/sw.js', {
      scope: '/app/',
      updateViaCache: 'none'
    });

    // 监听更新事件
    registration.addEventListener('updatefound', () => {
      const newWorker = registration.installing;
      newWorker.onstatechange = () => {
        if (newWorker.state === 'installed') {
          console.log('新版本已安装,等待激活');
        }
      };
    });

    // 强制更新
    document.getElementById('updateBtn').onclick = () => {
      registration.update();
    };

    // 显示通知
    registration.showNotification('新消息', {
      body: '您有未读消息',
      icon: '/icon.png',
      vibrate: [200, 100, 200]
    });

4.10 ServiceWorker相关类关系图

classDiagram %% 颜色标注上下文 class ServiceWorkerContainer { <> +controller: ServiceWorker +register(scriptURL): Promise +oncontrollerchange: EventHandler } class ServiceWorker { <> +scriptURL: String +state: "installing"|"installed"|"activating"|"activated"|"redundant" +postMessage(data): void } class ServiceWorkerGlobalScope { <> +caches: CacheStorage +clients: Clients +registration: ServiceWorkerRegistration +skipWaiting(): void +fetch(): Promise } class ServiceWorkerRegistration { <> +active: ServiceWorker +waiting: ServiceWorker +installing: ServiceWorker +update(): void } %% 关系箭头 ServiceWorkerContainer --> ServiceWorkerRegistration : register() 返回 ServiceWorkerContainer --> ServiceWorker : controller 引用 ServiceWorkerRegistration --> ServiceWorker : 管理版本\n(active/waiting/installing) ServiceWorkerGlobalScope --> ServiceWorkerRegistration : self.registration

总结:

对象 所在上下文 主要作用
ServiceWorkerContainer 客户端(Window) Service Worker 的入口管理器
ServiceWorkerRegistration 客户端/Service Worker 维护注册信息和生命周期状态
ServiceWorker 客户端 表示线程实例及其状态
ServiceWorkerGlobalScope Service Worker 线程 实际执行 Service Worker 代码的环境

五、总结

  • PWA是结合Web与原生体验的渐进式应用,核心特性包括离线支持、可安装及跨平台。
  • 相比传统Web,PWA具备后台更新、弱网优化(加载时间减少75%)和更高用户参与度(提升133%)。
  • 需通过manifest配置应用信息(名称/图标/显示方式)及Service Worker实现缓存管理、网络代理等核心功能,支持IndexedDB存储。
  • 其架构以单页应用为主,兼具原生应用的推送通知与硬件访问能力,分发方式灵活(URL+应用商店)。
相关推荐
小样还想跑13 分钟前
axios无感刷新token
前端·javascript·vue.js
Java水解22 分钟前
一文了解Blob文件格式,前端必备技能之一
前端
用户38022585982443 分钟前
vue3源码解析:响应式机制
前端·vue.js
bo521001 小时前
浏览器渲染机制详解(包含渲染流程、树结构、异步js)
前端·面试·浏览器
普通程序员1 小时前
Gemini CLI 新手安装与使用指南
前端·人工智能·后端
山有木兮木有枝_1 小时前
react受控模式和非受控模式(日历的实现)
前端·javascript·react.js
流口水的兔子1 小时前
作为一个新手,如果让你去用【微信小程序通过BLE实现与设备通讯】,你会怎么做,
前端·物联网·微信小程序
多啦C梦a1 小时前
🪄 用 React 玩转「图片识词 + 语音 TTS」:月影大佬的 AI 英语私教是怎么炼成的?
前端·react.js
呆呆的心1 小时前
大厂面试官都在问的 WEUI Uploader,源码里藏了多少干货?🤔
前端·微信·面试
heartmoonq1 小时前
深入理解 Vue 3 响应式系统原理:Proxy、Track 与 Trigger 的协奏曲
前端