Web Workers API

深入理解 Web Workers:创建和消息传递、Dedicated vs Shared vs Service Worker、importScripts 和 ES Module Worker、Comlink、以及 OffscreenCanvas。

Web Workers API

一、为什么需要 Web Workers

1.1 JavaScript 单线程的问题

JavaScript 在主线程运行,复杂的计算会阻塞 UI 响应。用户会感觉页面"卡住"了。

1.2 Web Workers 的解决方案

Web Workers 是在后台运行的独立线程,不会阻塞 UI:

  • 计算密集型任务可以在 Worker 中执行
  • 主线程保持响应
  • 通过消息传递与主线程通信

二、基本用法

2.1 创建 Dedicated Worker

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

// 发送消息到 Worker
worker.postMessage({ type: 'START', data: someData });

// 接收 Worker 的消息
worker.onmessage = function(event) {
  console.log('收到 Worker 消息:', event.data);
};

// 错误处理
worker.onerror = function(error) {
  console.error('Worker 错误:', error);
};
// worker.js
self.onmessage = function(event) {
  const { type, data } = event.data;

  if (type === 'START') {
    // 执行计算
    const result = processData(data);

    // 发送结果回主线程
    self.postMessage({ result });
  }
};

function processData(data) {
  // 耗时计算
  return data.map(item => item * 2);
}

2.2 terminate 和 close

// 主线程:终止 Worker
worker.terminate();

// Worker 自身:关闭自己
self.close();

三、Dedicated vs Shared vs Service Worker

3.1 Dedicated Worker

专用 Worker,只能被创建它的主线程使用:

// 只能当前脚本使用
const worker = new Worker('worker.js');

3.2 Shared Worker

共享 Worker,可以被多个脚本使用:

// 主线程 1
const sharedWorker = new SharedWorker('shared-worker.js');

// 主线程 2
const sharedWorker2 = new SharedWorker('shared-worker.js');

// shared-worker.js
self.onconnect = function(event) {
  const port = event.ports[0];

  port.onmessage = function(event) {
    console.log('收到消息:', event.data);
  };

  port.postMessage('你好');
};

3.3 与 Service Worker 的区别

特性 Dedicated Worker Shared Worker Service Worker
作用域 单页面 多页面 多页面
生命周期 页面关闭即结束 最后使用页面关闭 独立于页面
主要用途 计算密集任务 跨页面通信 网络拦截、推送
访问 API 有限 有限 缓存、推送

四、importScripts 和 ES Module Worker

4.1 importScripts

传统 Worker 使用 importScripts 导入脚本:

// worker.js
importScripts('utils.js', 'constants.js');

// 然后使用导入的函数
const result = processData(input);

4.2 ES Module Worker

现代浏览器支持 ES Module Worker:

// 主线程
const worker = new Worker('worker.js', { type: 'module' });

// worker.js
import { helper } from './utils.js';

self.onmessage = function(event) {
  const result = helper(event.data);
  self.postMessage(result);
};

五、Comlink

5.1 简化 Worker 通信

Comlink 是一个库,简化了 Worker 的消息传递:

// worker.js
import * as Comlink from 'https://cdn.jsdelivr.net/npm/[email protected]/+esm';

const workerAPI = {
  async processData(data) {
    return data.map(item => item * 2);
  }
};

Comlink.expose(workerAPI, self);
// 主线程
const worker = new Worker('worker.js', { type: 'module' });
const api = Comlink.wrap(worker);

// 像调用本地函数一样调用
const result = await api.processData([1, 2, 3]);

六、OffscreenCanvas

6.1 在 Worker 中绘制

OffscreenCanvas 允许在 Worker 中进行 Canvas 绘制:

// 主线程
const canvas = document.getElementById('myCanvas');
const offscreen = canvas.transferControlToOffscreen();

const worker = new Worker('canvas-worker.js', { type: 'module' });
worker.postMessage({ canvas: offscreen }, [offscreen]);
// canvas-worker.js
let ctx;

self.onmessage = function(event) {
  const canvas = event.data.canvas;
  ctx = canvas.getContext('2d');

  // 绘制
  ctx.fillStyle = 'blue';
  ctx.fillRect(0, 0, 100, 100);
};

七、适合 Worker 的计算

适合在 Worker 中执行的任务:

  • JSON 序列化/反序列化
  • 图像处理和滤镜
  • 视频/音频编解码
  • 大数据排序和搜索
  • 加密解密
  • 复杂数学计算

不适合在 Worker 中执行的任务:

  • 需要频繁 DOM 操作的
  • 需要 UI 更新的
  • 小数据量的简单计算

延展阅读