一、定义

JavaScript Workers 是一种允许在主线程之外运行 JavaScript 代码的机制,实现了真正的多线程编程。它们解决了 JavaScript 单线程执行模型的限制,允许在后台执行耗时任务,而不阻塞主线程的 UI 渲染和交互。

主线程和Worker的关系:JavaScript是单线程的,但为了处理耗时操作,引入了Web Workers,它们可以在后台运行,不阻塞主线程。

二、workers类型

JavaScript 中有三种主要类型的 Workers:

2.1 Web Workers (Dedicated Workers)
  • 专用 Worker,由单个主线程创建且专属于它,随着页面关闭而终止。

  • 最常用的 Worker 类型。

  • 与主线程通过消息传递进行通信。

2.2 Shared Workers
  • 共享 Worker,可以被同源的多个浏览器上下文(如不同标签页、iframe)共享。

  • 适用于需要在多个上下文间共享数据的场景。

  • 通信需要通过端口(port)进行。

2.3 Service Workers
  • 特殊类型的 Worker,作为网络代理运行。

  • 主要用于离线支持、后台同步、推送通知等。

  • 生命周期独立于网页。

  • 必须通过 HTTPS 运行。

三、workers核心特性

3.1 通信机制
  • 使用 postMessage() 方法发送消息。

  • 通过 onmessage 事件接收消息。

  • 消息会被序列化/反序列化,可以传递基本类型和可克隆对象。

3.2 限制
  • Workers 不能直接访问 DOM。

  • 不能使用某些主线程的 API(如 window 、document)。

  • 可以使用大部分 JavaScript API(如 fetch、setTimeout、IndexedDB)。

3.3 生命周期
  • Web Workers:由主线程创建,主线程或 Worker 自身可以终止。

  • Shared Workers:当没有脚本使用时自动终止。

  • Service Workers:有独立的生命周期(安装、激活、等待、激活、终止)。

四、Web Workers

Web Worker 的核心在于 线程隔离异步通信。它与主线程并行运行,但运行在完全独立的环境中。下面说说Web Worker能做什么和不能做什么?

能做什么✅

不能做什么🚫

执行计算密集型任务,如图像解码

直接操作 DOM、访问 document 对象

使用 XMLHttpRequest 或 fetch 发起网络请求

使用 window、parent 对象

通过 importScripts() 加载其他脚本

访问大部分全局变量或函数

基础示例

1. 主线程代码(main.js)

主线程负责创建Worker、发送(postMessage)任务和接收(onmessage)结果。

// 1. 创建Worker,指定后台脚本路径
const myWorker = new Worker('worker.js');

// 2. 发送消息,启动Worker任务
myWorker.postMessage({ numbers: [1, 2, 3, 4, 5] });

// 3. 监听来自Worker的消息
myWorker.onmessage = function(event) {
    console.log('计算结果:', event.data); // 输出:15
    // 收到结果后,可终止Worker以释放资源
    // myWorker.terminate();
};

// 4. 错误处理
myWorker.onerror = function(error) {
    console.error('Worker发生错误:', error.message);
};
2. Worker线程代码(worker.js)

Worker脚本监听主线程指令,执行任务并返回结果。

// 监听主线程发来的消息
self.onmessage = function(event) {
    const data = event.data;
    
    // 执行计算密集型任务(例如求和)
    const sum = data.numbers.reduce((a, b) => a + b, 0);
    
    // 将结果发送回主线程
    self.postMessage(sum);
    
    // Worker也可自行关闭:self.close()
};

关键点:主线程与Worker之间的所有数据都是通过复制(结构化克隆算法)来传递的,而非共享。对于大数据(ArrayBuffer),可以使用 可转移对象 来转移所有权,避免复制开销。

结构化克隆算法:

它是一种复制数据的方法,可以处理复杂的数据类型,比如对象、数组、日期等,但不是所有类型都支持,比如函数、DOM节点等。


复制和共享:

共享是多个线程访问同一块内存,而复制是各自有自己的副本。

可转移对象:

复制会有性能开销,使用可转移对象(Transferable Objects),当转移所有权后,原来的线程就不能再访问该对象了,新线程获得所有权,这样避免了复制的开销。

可转移对象示例:

// 主线程
const worker = new Worker('worker.js');
const buffer = new ArrayBuffer(1000000); // 1MB的二进制数据

// 第二个参数表示转移所有权
worker.postMessage(buffer, [buffer]);

console.log(buffer.byteLength); // 输出0,因为所有权已经转移。

结构化克隆 Vs 可转移对象:

传递方式

特点

适用场景

结构化克隆

复制数据副本,双方独立

小数据、需要保留原数据

可转移对象

转移数据所有权,发送方失去访问权

大数据、追求性能化

浏览器兼容性

Web Worker得到了几乎所有现代浏览器的广泛支持。为保障兼容,应在使用前进行特性检测:

if (typeof Worker !== 'undefined') {
    // 浏览器支持 Web Worker
    const myWorker = new Worker('worker.js');
} else {
    // 不支持,需提供降级方案(如改为在主线程异步执行)
    console.log('你的浏览器不支持 Web Worker。');
}

五、Shared Worker

Shared Worker(共享Worker) 是一种可以被多个浏览器上下文(如不同的标签页、iframe、甚至其他Worker)共享和连接的特殊Web Worker。它是实现同源页面间后台脚本与数据共享的核心机制。

可以将Shared Worker想象成一个运行在后台的独立中心。任何同源页面都可以连接至此中心,并通过端口与它进行独立的双向通信。

5.1 核心特性
5.1.1 多页面共享同一个Worker实例
  • 普通Worker是单页面独享的,而Shared Worker可以被同一域名下的多个页面共享。

  • 就像一个公共服务窗口,可以同时为多个用户提供服务。

5.1.2 基于端口(MessagePort)的通信机制
  • 每个连接到Shared Worker的页面都会获得一个独立的通信端口(MessagePort)。

  • Shared Worker通过这些端口与不同页面进行通信。

  • 通过 postMessage() 方法在页面与Shared Worker之间传递数据。

  • 支持结构化克隆算法复制数据,也支持可转移对象。

5.1.3 共享数据和状态
  • 所有连接到Shared Worker的页面可以共享Worker内部的数据。

  • 适合实现跨页面的数据同步和状态管理。

5.1.4. 独立的生命周期
  • 当第一个页面连接时创建并启动。

  • 只要有页面连接,Shared Worker就会保持运行。

  • 当所有页面断开连接后,Shared Worker会被终止。

5.1.5 独立的执行环境
  • 运行在主线程之外的独立线程中。

  • 拥有自己的全局作用域 SharedWorkerGlobalScope。

  • 无法直接访问DOM、window对象或页面的其他资源。

5.2 工作原理简单示例
5.2.1 Shared Worker 代码 (shared-worker.js)
// Shared Worker内部代码 (shared-worker.js)
// 保存所有连接的端口
const connections = [];

// 监听连接
self.onconnect = (event) => {
  const port = event.ports[0];
  
  // 将新端口加入连接列表
  connections.push(port);
  
  // 开始通信
  port.start();
  
  // 接收消息
  port.onmessage = (event) => {
    const message = event.data;
    
    // 向所有连接的端口广播消息
    connections.forEach(clientPort => {
      clientPort.postMessage(`Broadcast: ${message.message}`);
    });
  };
};
5.2.2 主线程代码
// 创建 Shared Worker
const worker = new SharedWorker('shared-worker.js');

// 连接到端口
const port = worker.port;

// 开始通信
port.start();

// 发送消息
port.postMessage({ message: 'Hello from Tab 1' });

// 接收消息
port.onmessage = (event) => {
  console.log('Received:', event.data);
};
5.3 适用场景
  • 跨页面数据同步(如购物车状态、用户登录状态)。

  • 多页面协作应用。

  • 共享计算资源(避免重复计算)。

  • 实时数据推送(如聊天应用)。

5.4 与普通Worker的主要区别

特性

Web Worker

Shared Worker

共享范围

单页面

多页面共享

通信方式

直接postMessage

通过 MessagePort

生命周期

页面关闭即终止

所有连接断开才终止

适用场景

单页面后台计算

多页面协作与共享

Shared Worker的核心价值在于提供了一种高效、安全的跨页面通信和数据共享机制,同时保持了Worker的线程隔离特性,避免了直接操作DOM的复杂性。

六、Service Workers

Service Workers 是一种运行在浏览器后台的脚本,与网页分离,拥有独立的生命周期。它们提供了强大的网络代理能力,允许开发者控制页面如何处理网络请求,并支持离线体验、推送通知等高级功能。

运行在浏览器后台的脚本,与网页分离:

想象你打开了一个购物网站(网页),这个网站有个「后台管家」(Service Workers)在浏览器的后台默默工作。

  • 即使你切换到其他标签页或暂时关闭购物网站,这个「管家」可能还在后台运行,不会跟着网页一起关闭。

  • 它和网页是分开的,就像你家的「管家」不会因为你出门就停止工作一样。

6.1 核心特性
6.1.1 网络代理与缓存控制

Service Workers 可以拦截和处理所有网络请求,实现自定义缓存策略:

管家会帮你「管理快递」(网络请求):

  • 当你浏览网站时,所有需要加载的图片、文字、数据都像「快递」,需要从服务器(快递公司)发送到你的浏览器。

  • 管家会拦截这些「快递请求」,然后决定:

    • 如果之前已经存了相同的「快递」(缓存),就直接拿给你,不用再等服务器发送,速度更快;

    • 如果是新「快递」,就去服务器取,同时还会把新「快递」存一份,下次备用。

// Service Worker 中拦截并缓存请求
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request)
      .then((response) => {
        // 如果缓存中有响应,则返回缓存内容
        if (response) {
          return response;
        }
        
        // 否则发起网络请求
        return fetch(event.request)
          .then((networkResponse) => {
            // 将新的响应添加到缓存
            if (networkResponse && networkResponse.status === 200) {
              const responseToCache = networkResponse.clone();
              caches.open('v1')
                .then((cache) => {
                  cache.put(event.request, responseToCache);
                });
            }
            return networkResponse;
          });
      })
  );
});
6.1.2 离线功能

通过缓存关键资源,Service Workers 可以让应用在离线时继续运行:

管家的「特殊技能」让网站更像手机APP:

  • 离线体验:即使你断网了,管家因为之前缓存了网站的核心内容(首页、商品列表等),你依然能浏览这些内容,就像没断网一样。

  • 推送通知:即使你没打开购物网站,管家也能帮你接收网站的「促销消息」(推送通知),比如「你关注的商品降价了」,直接显示在浏览器的通知栏里。

// 安装时缓存核心资源
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open('v1')
      .then((cache) => {
        return cache.addAll([
          '/',
          '/index.html',
          '/styles/main.css',
          '/script/main.js',
          '/images/logo.png'
        ]);
      })
  );
});
6.2 推送通知

支持后台接收推送消息并显示通知:

// 处理推送消息
self.addEventListener('push', (event) => {
  const data = event.data.json();
  const options = {
    body: data.body,
    icon: '/images/icon.png',
    badge: '/images/badge.png'
  };
  
  event.waitUntil(
    self.registration.showNotification(data.title, options)
  );
});

// 处理通知点击
self.addEventListener('notificationclick', (event) => {
  event.notification.close();
  
  event.waitUntil(
    clients.openWindow('https://example.com')
  );
});
6.3 生命周期

Service Workers 有严格的生命周期:

1. 注册 (Register):在主线程中注册 Service Worker。

// 主线程中注册 Service Worker
if ('serviceWorker' in navigator) {
   window.addEventListener('load', () => {
     navigator.serviceWorker.register('/sw.js')
       .then((registration) => {
         console.log('SW registered: ', registration);
       })
       .catch((registrationError) => {
         console.log('SW registration failed: ', registrationError);
       });
   });
}

2. 安装 (Install):首次注册时触发,用于缓存核心资源。

上班:你第一次访问网站时,浏览器会自动「雇佣」这个管家(安装 Service Workers)。

3. 激活 (Activate):安装完成后触发,用于清理旧缓存。

工作:安装完成后,管家正式开始工作,接管网站的网络请求、缓存等任务。

4. 等待 (Waiting):如果有旧版本的 Service Worker 正在运行,新版本会进入等待状态。 

5. 闲置 (Idle):Service Worker 处于休眠状态,等待事件触发。

6. 终止 (Terminate):浏览器可以随时终止闲置的 Service Worker 以节省资源。

休息(闲置/终止):如果一段时间没活干,浏览器会让管家暂时休息(终止)以节省资源。  

7. 唤醒 (Fetch/Push):当有 fetch、push 等事件触发时,Service Worker 会被唤醒。

唤醒(触发):当你再次访问网站、有网络请求或需要推送通知时,管家会被重新唤醒。  

七、三种 Workers 技术对比

特性

Web Workers

Shared Workers

Service Workers

基本概念

运行在后台的独立 JavaScript 线程,与主线程并行执行

可以被多个浏览上下文(如多个标签页、iframe)共享的 Worker

运行在浏览器后台的脚本,与网页分离,提供网络代理和离线能力

生命周期

随创建它的页面关闭而终止

当所有关联的浏览上下文关闭后终止

独立于页面生命周期,即使页面关闭仍可运行

通信方式

通过 postMessage() 和 onmessage 与主线程通信

通过 MessagePort 与多个页面通信,需要显式连接

通过 postMessage() 与页面通信,可监听 fetch、push 等事件

作用域

只能被创建它的页面访问

可以被同源的多个页面共享访问

可以控制同源的所有页面

应用场景

处理 CPU 密集型任务,避免阻塞主线程

多个页面共享计算结果或状态,如实时协作应用

离线功能、推送通知、后台同步、性能优化

网络访问

可以使用 XMLHttpRequest / fetch 发起网络请求

可以使用 XMLHttpRequest / fetch 发起网络请求

可以拦截和处理所有网络请求,实现自定义缓存策略

缓存能力

无内置缓存机制

无内置缓存机制

拥有 Cache API ,可缓存任意网络资源

浏览器支持

所有现代浏览器,IE10+

现代浏览器,IE 不支持

现代浏览器,需要 HTTPS(localhost 除外)

安全限制

无法访问 DOM,只能使用部分 Web API

无法访问 DOM,只能使用部分 Web API

无法访问 DOM,只能在 HTTPS 下运行(localhost 除外)

主要特性

  • 多线程执行

  • 非阻塞操作

  • 适合计算密集型任务

  • 跨页面共享

  • 节省资源

  • 适合数据共享场景

  • 离线优先

  • 推送通知

  • 后台同步

  • 网络代理

  • PWA 支持

创建方式

const worker = new Worker('worker.js');
const worker = new SharedWorker('shared-worker.js');
navigator.serviceWorker.register('sw.js');

通过合理使用 JavaScript Workers,可以显著提升 web 应用的性能和用户体验,特别是在处理复杂或耗时任务时。