返回首页

Web Worker,Service Worker及Shared Worker

布莱克2026-06-02 20:23已编辑
Tip:文章封面与内容无关,作者旅游时拍摄,因为没什么值得把四季都错过!

JavaScript 引擎是单线程的,浏览器(宿主环境)是多线程的

当打开一个页面时,浏览器通常会启动以下线程:

  • GUI 渲染线程:负责解析 HTML/CSS,绘制页面。
  • JS 引擎线程:负责执行你的 JS 代码(这就是我们常说的单线程)。
  • 事件触发线程:负责管理点击、滚动等交互。
  • 定时器触发线程:负责 setTimeout 的倒计时。
  • 网络请求线程:负责处理 AJAX/Fetch 请求

当创建一个 Worker 时,浏览器会做一件特别的事:它专门为这个 Worker 又启动了一个全新的“JS 引擎线程”。

  • 独立的堆栈:Worker 拥有自己独立的执行栈和内存空间。
  • 并行的能力:因为它是操作系统层面的独立线程,所以它能和主线程同时运行在 CPU 的不同核心上


Web worker

Web Worker 的核心价值就是:开辟一个独立的后台线程,专门跑这些复杂任务,让主线程专心处理用户界面,设计核心“完全隔离”。当执行 new Worker('task.js') 时,浏览器会为创建一个全新的 JS 执行环境

它拥有什么?(独立的)

  • 独立的 Event Loop:它有自己的宏任务和微任务队列,不会和主线程抢排队。
  • 独立的 Memory Space:它有自己的堆(Heap)和栈(Stack)。
  • 独立的全局对象 self:在 Worker 内部,没有 window,全局变量是 self(或者 globalThis)。

它没有什么?(严禁访问)

  • 无法操作 DOM:它看不见 document,也拿不到 querySelector。
  • 无法调用 UI 原生 API:比如 alert()、confirm()。
  • 无法共享内存(默认):它拿不到主线程定义的全局变量,只能通过“传话”(Messaging)来获取数据。


工作原理:

  • 主线程和 Worker 线程完全隔离,不能互相直接访问变量或 DOM。
  • 它们通过 postMessage 方法发送消息,通过 onmessage 事件接收消息来通信。
  • 数据是通过拷贝传递的,不是共享,所以没有传统多线程的锁问题

工作流程举例:

// main.js (主线程)
const worker = new Worker('worker.js');

// 发送数据给 Worker 去处理
worker.postMessage({ type: 'CALC', data: 1000000000 });

// 监听 Worker 处理完成的消息
worker.onmessage = (event) => {
  console.log('计算结果回来了:', event.data);
  // 更新页面 UI
  document.getElementById('result').innerText = event.data;
};

// worker.js (独立线程)
self.onmessage = (event) => {
  if (event.data.type === 'CALC') {
    let sum = 0;
    // 这是一个极其耗时的循环,但页面不会卡顿!
    for (let i = 0; i < event.data.data; i++) {
      sum += i;
    }
    // 把结果发回主线程
    self.postMessage(sum);
  }
};

应用场景

  • 大量数据计算:如在前端处理大型表格、数据聚合、复杂的数学建模。
  • 图像/视频处理:在后台处理 Canvas 像素数据,实现滤镜、缩放等。
  • 实时数据流处理:WebSocket 接收到大量数据,需要实时解析、计算时。
  • 大型文件解析:如在前端解析 ZIP、PDF、CSV 文件。


Service worker

Service Worker(简称 SW)最核心的能力是:拦截(Intercept)

传统网页严重依赖网络,一旦断网,页面就彻底无法访问,用户看到的会是“无法连接到网络”的提示,体验非常差

在没有 SW 之前,网页发出的请求(Fetch/XHR)是直接去互联网的。有了 SW 之后,所有的请求都要先经过它。它像一个中间人,可以决定:

  • 直接把请求发给服务器。
  • 拦截请求,从本地缓存(Cache Storage)里掏出旧数据给网页。
  • 甚至伪造一个响应(Response)回去。


工作原理

  • Service Worker 是一个独立于页面的 JS 脚本,它在浏览器后台运行。
  • 它必须使用 HTTPS(本地 localhost 开发除外)。
  • 它可以拦截页面发出的所有网络请求(fetch),并根据你的逻辑决定:是从网络获取,还是从本地缓存返回,甚至是在离线状态下生成一个备用页面。
  • 它依赖于一系列事件来触发:install、activate、fetch
注册 (Register)
    ↓
安装 (Install) ← 适合预缓存静态资源
    ↓
等待 (Waiting)  ← 关键!控制权交接的“冷静期”
    ↓
激活 (Activate) ← 适合清理旧缓存
    ↓
运行 (Fetch/Message) ← 拦截请求、后台同步等
    ↓
废弃 (Redundant) ← 被新版本取代或安装失败


核心应用场景:离线与加速

A. 离线访问(Offline First)

当网络掉线时,SW 拦截请求并返回缓存

B. 资源预取(Precaching)

在用户还没点击下一页时,SW 已经在后台偷偷把下一页的资源下载好了

Web Worker 随页面关闭而死亡,但 Service Worker 是独立于页面生命周期

当你注册了一个 Service Worker,浏览器会把它的脚本代码和它开辟的 Cache Storage(缓存库) 永久保存在用户的本地磁盘上。

  • 即使你关掉了浏览器:这个 SW 的配置信息依然在浏览器的系统文件中。
  • 即使你重启了电脑:只要你不手动清理浏览器缓存,它依然在那里“待命”
  • 休眠(Idle/Terminated):当你关闭所有标签页,且没有任务处理时,浏览器会杀掉 Service Worker 的运行进程。此时它不占内存。
  • 唤醒(Waking Up):当以下“闹钟”响起时,浏览器会瞬间把 Service Worker 从硬盘加载到内存里运行:

       用户再次打开网页:它立刻跳出来拦截第一个 fetch 请求。

       收到服务器推送(Push):即使网页没开,服务器发来个通知,SW 也会醒来弹出提示。

       后台同步(Sync):你之前没发出去的评论,在网络恢复的一刻,SW 自动醒来帮你在后台发出去。


Shared worker

SharedWorker 是一种可在多个同源浏览上下文(标签页、iframe、弹窗)之间共享的 Web Worker。它独立于所有页面运行,通过消息端口与各个页面通信

执行 new SharedWorker('/worker.js')
      │
      ├── 检查是否已有同名 Worker
      │
      ├── 没有 → 创建新 Worker
      │    └── worker.js 开始执行
      │         └── self.onconnect 等待连接
      │
      └── 已有 → 复用现有 Worker
           └── 触发 worker.js 的 connect 事件
                └── 新窗口端口加入数组
      │
      ▼
页面获得通信端口,可以收发消息
      │
      ▼
用户打开新标签页(同源)
      │
      ▼
新页面也执行 new SharedWorker('/worker.js')
      │
      └── 检测到已有 Worker → 复用
           └── 触发 worker.js 的 connect 事件
                └── 新窗口端口加入数组

Worker 内部:port.start() - 接收页面消息

// worker.js
self.addEventListener('connect', (e) => {
  const port = e.ports[0];
  
  // 🎯 必须调用:让 Worker 能够接收页面发来的消息
  port.start();
  
  // 现在可以监听页面消息了
  port.addEventListener('message', (msg) => {
    console.log('Worker 收到页面消息:', msg.data);
    // 处理业务逻辑...
  });
});

页面外部:worker.port.start() - 接收 Worker 消息

// 页面
const worker = new SharedWorker('worker.js');

// 🎯 必须调用:让页面能够接收 Worker 发来的消息
worker.port.start();

// 现在可以监听 Worker 消息了
worker.port.addEventListener('message', (e) => {
  console.log('页面收到 Worker 消息:', e.data);
});


assistant