Web Worker 性能优化
问题
如何使用 Web Worker 优化前端性能?Worker 有哪些类型?如何在主线程和 Worker 之间通信?
答案
JavaScript 是单线程的,复杂计算会阻塞 UI。Web Worker 允许在后台线程执行脚本,不阻塞主线程,是处理 CPU 密集型任务的利器。
Worker 类型对比
| 类型 | 作用域 | 生命周期 | 主要用途 |
|---|---|---|---|
| Dedicated Worker | 单页面 | 随页面关闭 | CPU 密集计算 |
| Shared Worker | 多页面 | 所有连接关闭 | 跨标签通信 |
| Service Worker | 域级别 | 独立于页面 | 离线缓存、推送 |
Dedicated Worker
基础用法
// main.ts - 主线程
const worker = new Worker(new URL('./worker.ts', import.meta.url));
// 发送消息
worker.postMessage({ type: 'calculate', data: [1, 2, 3] });
// 接收消息
worker.onmessage = (e: MessageEvent) => {
console.log('Result:', e.data);
};
// 错误处理
worker.onerror = (error) => {
console.error('Worker error:', error);
};
// 终止 Worker
worker.terminate();
// worker.ts - Worker 线程
self.onmessage = (e: MessageEvent) => {
const { type, data } = e.data;
if (type === 'calculate') {
const result = heavyCalculation(data);
self.postMessage(result);
}
};
function heavyCalculation(data: number[]): number {
// 复杂计算
return data.reduce((sum, n) => sum + n, 0);
}
封装 Promise Worker
// worker-wrapper.ts
type WorkerTask<T, R> = {
resolve: (value: R) => void;
reject: (error: Error) => void;
};
class PromiseWorker<T, R> {
private worker: Worker;
private taskId = 0;
private pending = new Map<number, WorkerTask<T, R>>();
constructor(workerUrl: URL) {
this.worker = new Worker(workerUrl);
this.worker.onmessage = (e: MessageEvent) => {
const { id, result, error } = e.data;
const task = this.pending.get(id);
if (task) {
if (error) {
task.reject(new Error(error));
} else {
task.resolve(result);
}
this.pending.delete(id);
}
};
}
postMessage(data: T): Promise<R> {
return new Promise((resolve, reject) => {
const id = ++this.taskId;
this.pending.set(id, { resolve, reject });
this.worker.postMessage({ id, data });
});
}
terminate() {
this.worker.terminate();
this.pending.forEach(task => {
task.reject(new Error('Worker terminated'));
});
this.pending.clear();
}
}
// worker.ts
self.onmessage = async (e: MessageEvent) => {
const { id, data } = e.data;
try {
const result = await processData(data);
self.postMessage({ id, result });
} catch (error) {
self.postMessage({ id, error: (error as Error).message });
}
};
// 使用
const worker = new PromiseWorker<number[], number>(
new URL('./worker.ts', import.meta.url)
);
const result = await worker.postMessage([1, 2, 3, 4, 5]);
Worker 线程池
避免创建过多 Worker,使用线程池管理。
class WorkerPool<T, R> {
private workers: Worker[] = [];
private taskQueue: Array<{
data: T;
resolve: (value: R) => void;
reject: (error: Error) => void;
}> = [];
private availableWorkers: Worker[] = [];
constructor(workerUrl: URL, poolSize = navigator.hardwareConcurrency || 4) {
for (let i = 0; i < poolSize; i++) {
const worker = new Worker(workerUrl);
worker.onmessage = (e: MessageEvent) => {
const { result, error, taskId } = e.data;
// 处理任务
// ...
// 重新加入可用队列
this.availableWorkers.push(worker);
this.processQueue();
};
this.workers.push(worker);
this.availableWorkers.push(worker);
}
}
execute(data: T): Promise<R> {
return new Promise((resolve, reject) => {
this.taskQueue.push({ data, resolve, reject });
this.processQueue();
});
}
private processQueue() {
while (this.taskQueue.length > 0 && this.availableWorkers.length > 0) {
const worker = this.availableWorkers.pop()!;
const task = this.taskQueue.shift()!;
worker.postMessage(task.data);
// 临时存储回调
(worker as any).__task = task;
}
}
terminate() {
this.workers.forEach(w => w.terminate());
this.workers = [];
this.availableWorkers = [];
this.taskQueue = [];
}
}
// 使用
const pool = new WorkerPool<ImageData, ImageData>(
new URL('./image-worker.ts', import.meta.url),
4
);
// 并行处理多个任务
const results = await Promise.all([
pool.execute(imageData1),
pool.execute(imageData2),
pool.execute(imageData3),
]);
Transferable Objects
Transferable Objects 可以零拷贝传输,避免序列化开销。
// 支持的类型
// - ArrayBuffer
// - MessagePort
// - ImageBitmap
// - OffscreenCanvas
// - ReadableStream
// - WritableStream
// - TransformStream
// main.ts
const buffer = new ArrayBuffer(1024 * 1024); // 1MB
// ❌ 普通传输 - 需要复制
worker.postMessage(buffer);
// ✅ 转移传输 - 零拷贝
worker.postMessage(buffer, [buffer]);
// 注意:传输后 buffer 在主线程不可用
console.log(buffer.byteLength); // 0
// 传输多个对象
const buffer1 = new ArrayBuffer(1000);
const buffer2 = new ArrayBuffer(2000);
worker.postMessage({ b1: buffer1, b2: buffer2 }, [buffer1, buffer2]);
图片处理示例
// main.ts
async function processImage(image: HTMLImageElement) {
// 创建 ImageBitmap
const bitmap = await createImageBitmap(image);
// 转移到 Worker
worker.postMessage({ bitmap }, [bitmap]);
}
// worker.ts
self.onmessage = async (e: MessageEvent) => {
const { bitmap } = e.data;
// 创建 OffscreenCanvas
const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
const ctx = canvas.getContext('2d')!;
ctx.drawImage(bitmap, 0, 0);
// 处理图片
const imageData = ctx.getImageData(0, 0, bitmap.width, bitmap.height);
applyFilter(imageData);
ctx.putImageData(imageData, 0, 0);
// 返回处理后的图片
const resultBitmap = await createImageBitmap(canvas);
self.postMessage({ bitmap: resultBitmap }, [resultBitmap]);
};
function applyFilter(imageData: ImageData) {
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
// 灰度处理
const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
data[i] = data[i + 1] = data[i + 2] = avg;
}
}
Shared Worker
多个页面共享同一个 Worker 实例。
// shared-worker.ts
const connections: MessagePort[] = [];
self.onconnect = (e: MessageEvent) => {
const port = e.ports[0];
connections.push(port);
port.onmessage = (e: MessageEvent) => {
const { type, data } = e.data;
if (type === 'broadcast') {
// 广播给所有连接
connections.forEach(conn => {
conn.postMessage({ type: 'message', data });
});
}
};
port.start();
};
// page.ts
const worker = new SharedWorker(new URL('./shared-worker.ts', import.meta.url));
const port = worker.port;
port.onmessage = (e: MessageEvent) => {
console.log('Received:', e.data);
};
port.start();
// 发送消息
port.postMessage({ type: 'broadcast', data: 'Hello from page!' });
Service Worker 缓存
// sw.ts
const CACHE_NAME = 'v1';
const STATIC_ASSETS = [
'/',
'/index.html',
'/styles.css',
'/app.js',
];
// 安装事件:缓存静态资源
self.addEventListener('install', (event: ExtendableEvent) => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache => {
return cache.addAll(STATIC_ASSETS);
})
);
});
// 激活事件:清理旧缓存
self.addEventListener('activate', (event: ExtendableEvent) => {
event.waitUntil(
caches.keys().then(keys => {
return Promise.all(
keys
.filter(key => key !== CACHE_NAME)
.map(key => caches.delete(key))
);
})
);
});
// 请求拦截:缓存优先策略
self.addEventListener('fetch', (event: FetchEvent) => {
event.respondWith(
caches.match(event.request).then(cached => {
return cached || fetch(event.request).then(response => {
// 缓存新资源
const responseClone = response.clone();
caches.open(CACHE_NAME).then(cache => {
cache.put(event.request, responseClone);
});
return response;
});
})
);
});
// 注册 Service Worker
// main.ts
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js', { scope: '/' })
.then(registration => {
console.log('SW registered:', registration.scope);
})
.catch(error => {
console.error('SW registration failed:', error);
});
}
缓存策略
| 策略 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Cache First | 快速响应 | 可能过期 | 静态资源 |
| Network First | 数据最新 | 依赖网络 | API 请求 |
| Stale While Revalidate | 快速+更新 | 略复杂 | 可容忍短暂过期 |
实际应用场景
1. 大数据处理
// 在 Worker 中处理大量数据
// worker.ts
self.onmessage = (e: MessageEvent) => {
const { data } = e.data;
// 排序、过滤、聚合等操作
const sorted = data.sort((a, b) => a.value - b.value);
const filtered = sorted.filter(item => item.active);
const aggregated = filtered.reduce((acc, item) => {
acc[item.category] = (acc[item.category] || 0) + item.value;
return acc;
}, {});
self.postMessage(aggregated);
};
2. 图像处理
// 在 Worker 中处理图片
self.onmessage = async (e: MessageEvent) => {
const { imageData, filter } = e.data;
switch (filter) {
case 'blur':
applyBlur(imageData);
break;
case 'sharpen':
applySharpen(imageData);
break;
case 'grayscale':
applyGrayscale(imageData);
break;
}
self.postMessage({ imageData }, [imageData.data.buffer]);
};
3. JSON 解析
// 大 JSON 解析
// main.ts
async function parseLargeJSON(jsonString: string) {
const worker = new PromiseWorker<string, object>(
new URL('./json-worker.ts', import.meta.url)
);
return worker.postMessage(jsonString);
}
// json-worker.ts
self.onmessage = (e: MessageEvent) => {
try {
const result = JSON.parse(e.data);
self.postMessage({ result });
} catch (error) {
self.postMessage({ error: (error as Error).message });
}
};
4. WebAssembly
// 在 Worker 中运行 WASM
// worker.ts
let wasmModule: WebAssembly.Instance;
async function initWasm() {
const response = await fetch('/algorithm.wasm');
const bytes = await response.arrayBuffer();
const { instance } = await WebAssembly.instantiate(bytes);
wasmModule = instance;
}
self.onmessage = async (e: MessageEvent) => {
if (!wasmModule) {
await initWasm();
}
const { fn, args } = e.data;
const result = (wasmModule.exports[fn] as Function)(...args);
self.postMessage({ result });
};
5. AI 前端场景
Web Worker 在 AI 前端应用中有多个关键用途,能有效避免 AI 密集计算阻塞 UI。
workers/ai-tasks-worker.ts
// AI 前端中 Worker 的典型应用
// ---- 场景 1:Tokenizer 计算 ----
// 统计输入 token 数量(用于展示剩余 token 额度)
// Tokenizer 初始化和编码是 CPU 密集型操作,必须放 Worker
import { AutoTokenizer } from '@huggingface/transformers';
let tokenizer: Awaited<ReturnType<typeof AutoTokenizer.from_pretrained>> | null = null;
async function initTokenizer() {
// 加载 tokenizer(~5MB),在 Worker 中不阻塞主线程
tokenizer = await AutoTokenizer.from_pretrained('Xenova/gpt-4o');
}
function countTokens(text: string): number {
if (!tokenizer) throw new Error('Tokenizer 未初始化');
const encoded = tokenizer.encode(text);
return encoded.length;
}
// ---- 场景 2:流式 Markdown 解析 ----
// 将 Markdown→HTML 转换放在 Worker 中,避免主线程做重计算
import { marked } from 'marked';
function parseMarkdown(text: string): string {
return marked.parse(text, { async: false }) as string;
}
// ---- 场景 3:端侧 Embedding 推理 ----
// 浏览器端生成文本向量(用于本地知识库搜索)
import { pipeline } from '@huggingface/transformers';
let embedder: Awaited<ReturnType<typeof pipeline>> | null = null;
async function initEmbedder() {
embedder = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2');
}
async function embed(text: string): Promise<Float32Array> {
if (!embedder) throw new Error('Embedding 模型未初始化');
const output = await embedder(text, { pooling: 'mean', normalize: true });
return output.data as Float32Array;
}
// ---- 场景 4:大 JSON 响应解析 ----
// AI 工具调用(Function Calling)返回的结构化数据可能很大
function parseToolResult(jsonString: string): unknown {
return JSON.parse(jsonString);
}
// ---- 统一消息分发 ----
self.onmessage = async (e: MessageEvent) => {
const { type, payload, id } = e.data;
try {
let result: unknown;
switch (type) {
case 'init-tokenizer': await initTokenizer(); result = 'ok'; break;
case 'count-tokens': result = countTokens(payload); break;
case 'parse-markdown': result = parseMarkdown(payload); break;
case 'init-embedder': await initEmbedder(); result = 'ok'; break;
case 'embed': result = await embed(payload); break;
case 'parse-json': result = parseToolResult(payload); break;
}
self.postMessage({ id, result });
} catch (error) {
self.postMessage({ id, error: (error as Error).message });
}
};
| AI 场景 | Worker 中执行的任务 | 为什么需要 Worker |
|---|---|---|
| Token 计数 | Tokenizer 编码 | 长文本编码耗时 10-50ms |
| 流式 Markdown | Markdown→HTML 转换 | 长文本解析占 CPU |
| 本地推理 | Embedding/分类/检测 | 模型推理 50-500ms |
| 大 JSON 解析 | 工具调用结果解析 | >1MB JSON 解析耗时 |
| 文件 Hash | 大文件上传前计算 SHA-256 | 10MB 文件 Hash ~100ms |
| 语音处理 | 音频预处理(重采样、VAD) | 音频数据量大 |
更多 AI 端侧推理参考 Web AI 与端侧推理。
常见面试问题
Q1: Web Worker 有什么限制?
答案:
| 限制 | 说明 |
|---|---|
| 无法访问 DOM | 不能操作 document、window |
| 无法访问 window 对象 | 使用 self 代替 |
| 同源限制 | Worker 脚本必须同源 |
| 无法使用部分 API | 如 alert、confirm |
| 文件限制 | 不能访问本地文件系统 |
// Worker 中可用的 API
self.fetch()
self.indexedDB
self.caches
self.WebSocket
self.crypto
self.performance
Q2: 如何实现主线程和 Worker 的通信?
答案:
// 1. postMessage + onmessage
// 主线程 → Worker
worker.postMessage(data);
// Worker → 主线程
self.postMessage(result);
// 2. Transferable Objects 高效传输
const buffer = new ArrayBuffer(1000);
worker.postMessage(buffer, [buffer]); // 转移所有权
// 3. SharedArrayBuffer 共享内存
const sab = new SharedArrayBuffer(1024);
const arr = new Int32Array(sab);
worker.postMessage({ buffer: sab });
// 两边都可以读写 arr
Q3: 什么时候应该使用 Web Worker?
答案:
| 场景 | 是否使用 Worker | 原因 |
|---|---|---|
| 复杂计算 | ✅ | 避免阻塞 UI |
| 大数据处理 | ✅ | 不阻塞交互 |
| 图片处理 | ✅ | 计算密集 |
| 简单交互 | ❌ | 通信开销 |
| DOM 操作 | ❌ | 不支持 |
| 小数据量 | ❌ | 不值得 |
Q4: Service Worker 和 Web Worker 的区别?
答案:
| 特性 | Web Worker | Service Worker |
|---|---|---|
| 用途 | 后台计算 | 网络代理/缓存 |
| 生命周期 | 随页面 | 独立于页面 |
| API | 有限 | Fetch/Cache/Push |
| 作用域 | 页面级 | 域级别 |
| HTTPS | 不要求 | 必须(localhost 除外) |
Q5: 如何优化 Worker 通信性能?
答案:
// 1. 使用 Transferable Objects
const buffer = new ArrayBuffer(largeSize);
worker.postMessage(buffer, [buffer]); // 零拷贝
// 2. 批量发送消息
const batch = [];
for (const item of items) {
batch.push(processItem(item));
if (batch.length >= 100) {
worker.postMessage(batch);
batch.length = 0;
}
}
// 3. 使用 SharedArrayBuffer
const sab = new SharedArrayBuffer(1024);
// 主线程和 Worker 共享内存
Atomics.store(new Int32Array(sab), 0, value);
// 4. 避免频繁创建 Worker
// 使用 Worker 池复用