悟夕导航

HTML Web Workers:让 JavaScript“多线程”的指南

61 0 0
一句话:Worker 把耗时任务扔到后台线程,主页面继续流畅滚动,还能实时拿结果。下面从“建、通、关”到“传大文件、跨线程”一次讲透,复制即可跑。

1. 为什么需要 Worker?

JavaScript 是单线程,死循环 1 秒 = 页面卡 1 秒
Web Workers 提供真正的操作系统级线程,特点:

  • 与主线程并行,不阻塞 UI
  • 无法操作 DOM(线程安全)
  • 同源策略,必须通过消息通信
  • 支持 ES6+、import 模块、fetch、IndexedDB 等

适用场景:
大量计算、图像/音频处理、轮询 WebSocket、加密/压缩、预加载


2. 30 秒搭一个“Hello Worker”

main.js(主线程)

const worker = new Worker('./worker.js'); // 1. 新建线程

worker.postMessage({ num: 42 });          // 2. 发数据

worker.onmessage = (e) => {               // 3. 收结果
  console.log('来自Worker:', e.data);
};

worker.onerror = (err) => console.error(err);

worker.js(后台线程)

// self 代表 Worker 全局
self.onmessage = (e) => {
  const result = e.data.num * 2; // 假装算很久
  self.postMessage(result);      // 把结果送回主线程
};
文件必须同源通过 HTTP/S 打开;双击本地 HTML 会报跨域 。

3. 消息通信格式:想传啥就传啥(几乎)

主线程 ↔ Worker 之间结构化克隆(Structured Clone),支持:

  • 基本类型、对象、数组、Map、Set
  • File、Blob、ArrayBuffer、ImageData
  • 循环引用也没事函数、DOM 节点会报错

示例:一次扔 100MB 大文件也 OK

worker.postMessage({ file: bigFileBlob }); // 主线程

4. 实战 1:秒算斐波那契(再也不卡页面)

index.html

<input id="n" type="number" value="40">
<button onclick="calc()">普通算</button>
<button onclick="calcWorker()">Worker算</button>
<div id="result"></div>

main.js

function calc() { // 主线程直接算——必卡
  const res = fibonacci(+n.value);
  result.textContent = res;
}

function calcWorker() {
  const worker = new Worker('./fib-worker.js');
  worker.postMessage(+n.value);
  worker.onmessage = e => {
    result.textContent = e.data;
    worker.terminate(); // 算完即毁,省内存
  };
}

function fibonacci(n) { return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2); }

fib-worker.js

self.onmessage = (e) => {
  const n = e.data;
  const res = fib(n); // 同逻辑,但在后台
  self.postMessage(res);
};

function fib(n) { return n < 2 ? n : fib(n - 1) + fib(n - 2); }
点“Worker算”时输入框仍能响应,UI 帧率 60 FPS 。

5. 实战 2:多文件并行哈希(import 外部库)

worker.js

importScripts('https://cdn.jsdelivr.net/npm/crypto-js@4/crypto-js.js'); // 引入第三方库

self.onmessage = async (e) => {
  const file = e.data; // File 对象
  const buffer = await file.arrayBuffer();
  const hash = CryptoJS.SHA256(buffer).toString();
  self.postMessage({ name: file.name, hash });
};

主线程循环发送即可并行计算,CPU 多核吃满。


6. 共享内存 SharedArrayBuffer(高阶可选)

// main.js
const sab = new SharedArrayBuffer(1024); // 1KB 共享内存
const worker = new Worker('worker.js');
worker.postMessage(sab);
// worker 直接读写同一块内存,零拷贝,游戏物理引擎利器
因安全攻击(Spectre),目前需 跨源隔离(COOP/COEP 头)才能开启 ;普通业务先不用。

7. Worker 生命周期 & 调试

  1. 新建new Worker(url)
  2. 运行 → 执行顶层代码;onmessage 等待数据
  3. 终止worker.terminate() 立即杀线程(无法复活)
  4. 调试 → Chrome DevTools → Sources → Worker threads 可断点

8. 异常与性能坑

说明急救
跨域加载file:/// 协议会报错http(s):// 本地服务器
大对象复制100MB 深克隆会卡一下改用 Transferable(零拷贝)
内存泄漏newterminate算完即毁,或复用单例 Worker
DOM 操作在 Worker 里写 document 直接抛错把结果发回主线程再改 DOM

9. 现代化:模块 Worker(import/export)

// main.js
const worker = new Worker('./calc.js', { type: 'module' }); // 关键:type='module'

// calc.js
import { sha256 } from './hash.js'; // 现在能用 ESModule 了
export default self.onmessage = async (e) => { … };
2024 年主流浏览器已支持,开发阶段直接 ESM,打包阶段可转 iife 兼容旧浏览器。

10. 一句话口诀(背完走天下)

“new 出线程,postMessage 传,onmessage 收,算完 terminate 清;
文件大用 Transferable,ESModule 写现代码;
不碰 DOM 零跨域,内存泄漏要牢记。”

掌握这 10 行心法,下次产品说“前端要算 10 万条哈希”——你就能淡定回答:
“上 Worker,页面不会卡。”

0
快来点个赞吧

发表评论

隐私评论

评论列表

来写一个评论吧