主题
13 · Web Workers 与 OffscreenCanvas
目标:把「会卡 UI 的纯计算」挪到 Worker;理解
postMessage通信模型;知道 OffscreenCanvas 适用边界,避免为炫技上 Worker。
官方主文:Web Workers API · OffscreenCanvas
1. 背景与目标
主线程同时负责 JS 执行、布局、绘制与事件响应。一次 200ms 的 JSON 解析或图像滤镜,就会让点击、滚动、输入「跟手变差」。
Worker 在独立线程跑 JS,通过消息与主线程协作——不能直接操作 DOM,也不能共享可变对象(除非 SharedArrayBuffer,本篇不展开)。
值得上 Worker 的信号:
- Performance 面板里 Long Task 集中在纯计算,与布局无关
- 用户操作与计算可异步(导出、压缩、批量解析)
- 数据可序列化传入传出
不必上 Worker:轻量 filter/map、本来就该服务端做的重活、为了面试名词硬拆。
2. 核心概念
| 概念 | 说明 |
|---|---|
| Dedicated Worker | 最常见;一页面一脚本,new Worker(url) |
postMessage | 结构化克隆传数据;大对象有拷贝成本 |
| Transferable | ArrayBuffer 等可「转移所有权」零拷贝 |
| Worker 内 API | fetch、crypto、部分 Canvas;无 document/window |
| OffscreenCanvas | 在 Worker 里绑 WebGL/2D 上下文,主线程只收位图或纹理 |
通信模型一句话:主线程发任务 → Worker 算完回结果 → 主线程只负责渲染与交互。
3. 最小实践:Worker 里算斐波那契(刻意阻塞对比)
src/workers/fib.worker.ts:
ts
// 纯计算,不碰 DOM
self.onmessage = (e: MessageEvent<{ n: number }>) => {
const { n } = e.data
const fib = (x: number): number => (x <= 1 ? x : fib(x - 1) + fib(x - 2))
const result = fib(n)
self.postMessage({ result })
}1
2
3
4
5
6
7
2
3
4
5
6
7
主线程 main.ts:
ts
const worker = new Worker(new URL('./workers/fib.worker.ts', import.meta.url), {
type: 'module',
})
worker.onmessage = (e) => {
console.log('fib =', e.data.result)
statusEl.textContent = `结果:${e.data.result}`
}
btn.onclick = () => {
statusEl.textContent = '计算中…'
worker.postMessage({ n: 42 })
}
// 对照组:主线程直接算 fib(42) —— 点按钮时输入框会卡1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Vite / Webpack 5 用 new URL(..., import.meta.url) 打包 Worker;uni-app H5 可行,小程序需用分包 Worker 能力,能力因平台而异。
OffscreenCanvas 速览
ts
// 主线程:把 canvas 控制权交给 Worker
const offscreen = canvas.transferControlToOffscreen()
worker.postMessage({ canvas: offscreen }, [offscreen])
// Worker:在独立线程绘制
const ctx = offscreen.getContext('2d')
// 每帧画完可 postMessage ImageBitmap 回主线程展示1
2
3
4
5
6
7
2
3
4
5
6
7
适合:粒子、波形、大量 2D 重绘且主线程已吃紧。多数业务图表用 CSS/DOM 或 ECharts 即可,不必默认 OffscreenCanvas。
4. 踩坑与取舍
| 坑 | 对策 |
|---|---|
频繁 postMessage 小对象 | 合并批次;大数据用 Transferable |
| 在 Worker 里改共享对象 | 不行;每次消息都是克隆 |
| Worker 里引第三方大包 | Worker 也占体积;单独拆 chunk |
| 调试困难 | Chrome Sources → Threads;console 在 Worker 可用 |
| 小程序 / 旧 WebView | 先查运行时支持表,再设计降级(主线程 + 分片 requestIdleCallback) |
何时不值得:计算 < 16ms、强依赖 DOM 读写、团队无人维护双线程代码——先优化算法或 Web Worker 以外的手段(分页、懒算)。
5. 验收清单
- [ ] 同一重计算,主线程 vs Worker 各跑一遍,用 Performance 对比 Long Task
- [ ] 能说清「结构化克隆」与 Transferable 区别
- [ ] 能画一张「主线程 ↔ Worker」消息时序(用户点击 → postMessage → onmessage → 更新 UI)
- [ ] 知道 OffscreenCanvas 是「绘制 offload」,不是万能性能药
6. 下一步
→ 14 · 动画体系:CSS / WAAPI / 库的取舍
