跳到主要内容

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
流式 MarkdownMarkdown→HTML 转换长文本解析占 CPU
本地推理Embedding/分类/检测模型推理 50-500ms
大 JSON 解析工具调用结果解析>1MB JSON 解析耗时
文件 Hash大文件上传前计算 SHA-25610MB 文件 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 WorkerService 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 池复用

相关链接