Service Worker 是浏览器后台独立线程,可拦截请求、缓存资源实现离线访问,HTTPS 环境运行,是 PWA 核心能力。

占用大小:不确定,空间不足时,会通过 LRU 策略回收长期未被访问的缓存

特性

  • 必须通过 HTTPS 协议传输
  • 单独线程,不会阻塞主线程,也无法访问 DOM,和页面独立,页面关闭后还能运行
  • 只能缓存 Service Worker 同子级目录下的文件,目录外会缓存失败
  • 基于事件驱动来响应操作,有相关的生命周期
  • 通过拦截用户请求,配合 Cache Storage 匹配内容执行缓存机制

基础使用流程

分为两步:定义 Service Worker 文件 和 注册 Service Worker

定义 Service Worker

  1. 定义需要缓存的资源
  2. 在 install 事件中注册缓存
  3. 在 activate 事件中删除旧缓存
  4. 在 fetch 事件中
// sw.js
// 缓存名称
const CACHE_NAME = 'my-cache-v1';

// 需要缓存的资源
const urlsToCache = [
  '/index.html',
  '/style.css',
  '/script.js'
];

// install 事件:缓存资源
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME).then(cache => {
      return cache.addAll(urlsToCache);
    })
  );
  self.skipWaiting(); // 安装完成后立即进入 activate
});

// activate 事件:删除旧缓存
self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(cacheNames => {
      return Promise.all(
        cacheNames.map(name => {
          if (name !== CACHE_NAME) {
            return caches.delete(name);
          }
        })
      );
    })
  );
  self.clients.claim(); // 立即控制页面
});

// fetch 事件:只从缓存读取
self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request).then(response => {
      return response || new Response('cache 不存在', { status: 404 });
    })
  );
});
// index.html
// 注册 Service Worker
navigator.serviceWorker.register('/sw.js',{
	// 定义只能控制 /app/ 路径下的文件
	// 默认是对 sw.js 同级以下的路径进行控制,这里是 '/'
	// 只对 sw.js 所在路径 '/' 和子目录进行指定,超出目录范围则会失效
	scope:'/app/'
})
	.then(() => console.log('Service Worker 注册成功'))
	.catch(err => console.error('Service Worker 注册失败:', err));

注册 Service Worker

在页面周期内注册 Service Worker 文件

// index.html
// 注册 Service Worker
navigator.serviceWorker.register('/sw.js',{
	// 定义只能控制 /app/ 路径下的文件
	// 默认是对 sw.js 同级以下的路径进行控制,这里是 '/'
	// 只对 sw.js 所在路径 '/' 和子目录进行指定,超出目录范围则会失效
	scope:'/app/'
})
	.then(() => console.log('Service Worker 注册成功'))
	.catch(err => console.error('Service Worker 注册失败:', err));

生命周期

⛔:代表当前事件无法监听,只是概念上的生命周期

注册阶段(⛔)

首次执行 navigator.serviceWorker.register('/sw.js'),尝试安装内容,此时未开始接管页面内容

  • 安装失败,结束流程
  • 安装成功进入下个流程
navigator.serviceWorker.register('/sw.js')
install

首次安装注册/文件变更时触发,该阶段用于预缓存

  • 通过 caches.add()/caches.addAll([]) 安装内容,此时会立刻发起请求去缓存对应的内容
  • event.waitUtil 用于告诉 Service Worker 等到异步事件执行完成后,再把 install 生命周期结束,避免提前结束导致缓存操作异常
// sw.js
self.addEventListener('install', event => {
	event.waitUntil(
		caches.open('v1').then(cache => cache.addAll(['/index.html']))
	);
});
worker in waiting (install → waiting)(⛔)

存在旧 Service Worker 运行,新 Service Worker 接管时会进入 worker in waiting 阶段,等待旧 Service Worker 完成工作并退出后,新 Service Worker 接管工作,就会进入下个状态

  • 通过 self.skipWaiting() 可以强制跳过等待
// sw.js
self.addEventListener('install', event => {
	event.waitUntil(
		caches.open('v1').then(cache => cache.addAll(['/index.html'])).then(()=>{
			// 在 install 事件中调用,跳过旧 SW 的释放逻辑
			self.skipWaiting();
		})
	);
});
activate

可以用于清理旧缓存,准备接管客户端页面,使用 caches.delete() 清理旧缓存的内容

经历过该阶段后,Service Worker 正式开始工作阶段

  • self.clients.claim() 告诉浏览器 Service Worker 立即接管所有未被接管的页面,无需让用户重新加载页面等操作
self.addEventListener('activate', event => {
  event.waitUntil(
		// 清除所有旧缓存
    caches.keys().then(keys =>
      Promise.all(
        keys.map(key => key !== 'v2' && caches.delete(key))
      )
    )
  );
  self.clients.claim(); // 告诉浏览器接管所有开启的界面
});
runing 运行中(⛔)

该阶段正式进入运行状态,可以执行对应的操作

fetch

监听网络请求,通过 caches.matches 去决定读取缓存还是继续发起请求

self.addEventListener('fetch', event => {
  event.respondWith(/* 返回缓存或网络 */);
});
push

接收服务器推送的信息

self.addEventListener('push', event => {
  // 处理推送
});
sync

处理网络恢复后的同步任务

self.addEventListener('sync', event => {...});
Idle/terminated(⛔)

空闲时状态,此时 Service Worker 会终止

  • 当空闲时,浏览器会终止 Service Worker 执行,不会后台常驻(无法保持长时间运行)
  • 当 fetch、push、sync 再次触发时,会恢复 Service Worker 运行

Update Flow(Service Worker 更新流程)

更新流程 = 新 SW 的 install → waiting → activate 流程 + 与旧 SW 的交替逻辑。

  1. 浏览器下载新的 sw.js
  2. 对比旧版本的 sw.js 内容是否变化
  3. 若有变化 → 新 SW 进入 installing
  4. 新 SW install 完成 → new SW 进入 waiting
  5. 旧 SW 仍在工作(activated)
  6. 当旧 SW 停止(或 skipWaiting() 出现)→ activate 新 SW

fetch 缓存方案

对于前端开发,可能使用 Service Worker 更多的是在网路请求缓存这块
但还是要结合实际业务场景判断是否有需求,因为目前浏览器缓存已经能满足大部分需求

1. 缓存优先(Cache First)

先从缓存里拿数据,如果没有缓存,就执行网络请求
应用场景:适合长期缓存的资源

  • JS/CSS/图片等静态资源
  • 长期无需更新的资源,比如说 logo
  • 有离线支持需求的资源:PWA,离线编辑器等
self.addEventListener('fetch',(event)=>{
	event.respondWith(
		caches.match(event.request).then(cached => {
			// 根据请求先读缓存,在发送请求
			return cached || fetch(event.request);
		})
	);
})
2. 网络优先(NetWork First)

请求网络,获取最新数据,如果失败再从缓存里拿取数据

应用场景**:**适合 fallback 相关场景

  • HTML 静态页面,作为离线替代页面存在
  • 首屏新闻、商品列表的离线缓存替代展示
  • 用户个人数据,获取失败可以通过离线缓存读取
self.addEventListener('fetch',(event)=>{
	event.respondWith(
		fetch(event.request)
		.then(response => {
			// 因为 response 只能被读取一次,所有要用 clone 复制 响应
			const clone = response.clone();

			// 打开缓存,更新对应的响应结果
			caches.open(CACHE_NAME).then(cache => cache.put(event.request, clone));
			return response;
		})
		// 如果请求异常/失败,返回缓存结果
		.catch(() => caches.match(event.request))
	);
})
3. 陈旧返回,后台更新( Stale-While-Revalidate )

立刻返回旧缓存,然后在后台执行 fetch 异步更新缓存
这样用户后续就可以得到新缓存

应用场景:对内容新鲜度要求有一定容忍的场景

  • 图片资源
  • CDN 静态缓存资源
  • 和视图无关的资源,例如一些插件等
self.addEventListener('fetch',(event)=>{
	event.respondWith(
		caches.match(event.request).then(cached => {
			// 执行异步请求
			const fetchPromise = fetch(event.request).then(networkRes => {
				//  返回请求的异步更新结果
				caches.open('sw-cache').then(cache => cache.put(event.request, networkRes.clone()));
				return networkRes;
			});

			// 不等待,直接立刻响应
			return cached || fetchPromise;
		})
	);
})
4. 网络回退缓存( Cache With Network Fallback )

是网络优先(Network First) 的简易版本,不更新缓存,只要失败就读取缓存数据

应用场景:对缓存新鲜度要求不高的场景

  • HTML 说明文档
  • 初始化界面信息
event.respondWith(
  fetch(event.request).catch(() => caches.match(event.request))
);

思考

1. 缓存了 Service Worker 文件,是否会导致 Service Worker 不会更新?

Service Worker 是通过字节码比对文件情况来执行更新的,所以对于同名文件,只要请求 sw.js 出现变化,就会更新,即使是通过 cache.add() 缓存起来,也会去请求最新文件,不会执行缓存。

最大概率导致无法执行更新情况:网络层意外配置了 sw.js 强缓存而没有协商缓存

为了避免其他意外情况导致无法更新 Service Worker,可以通过主动触发 Service Worker 检查更新机制来避免情况发生

navigator.serviceWorker.register('/sw.js').then(registration=>{
  // 主动检查更新
  registration.update().catch(err => {
    console.log('SW 检查异常:', err);
  });
})

总结

  • Service Worker 生命周期:注册→ install(预缓存)→ waiting(待旧 SW 退出)→activate(接管页面)→running(处理 fetch/push/sync 事件)→ idle/terminated(空闲终止)。
  • Service Woker 离线缓存使用流程:定义 SW 文件 → 监听 install 添加缓存 、 activate 中处理旧缓存 、fetch 中匹配网络缓存 → 注册 SW
  • 使用时应当注意强缓存导致 Service Worker 无法及时更新的问题

参考内容

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐