javascript的web workers如何使用_它如何实现多线程编程以避免阻塞ui【教程】

Web Workers 不能直接操作 DOM,因其运行在独立线程且无 window、document 等浏览器 API,仅支持 setTimeout、fetch 等有限接口;通信依赖 postMessage/onmessage,数据需序列化,大对象应使用 transferable 优化。

Web Workers 不能直接操作 DOM,这是根本前提

很多初学者一上来就想在 Worker 里调用 document.getElementById 或修改 innerHTML,结果什么也不发生,控制台也没报错——因为 Web Workers 运行在完全独立的线程和全局环境里,没有 windowdocumentalert 等任何浏览器 DOM API。它只有自己的 self(等价于 globalThis),能用的只有 setTimeoutfetchatobstructuredClone 等有限的 API。

创建 Worker 的两种方式:内联脚本 vs 外部文件

必须用外部 JS 文件才能启动 Worker,这是硬性限制。所谓“内联”,只是用 Blob + URL.createObjectURL 模拟,本质仍是加载一个 URL。直接传字符串或函数会报 DOMException: Failed to construct 'Worker': Script URL must be of scheme 'http' or 'https'

  • 推荐方式:单独建 worker.js 文件,用 new Worker('./worker.js')
  • 临时调试可用 Blob 方式,但注意每次都要 revokeObjectURL 防止内存泄漏
  • 模块化 Worker 需加 type: 'module' 参数,且 importScripts 不支持 ES 模块语法
const workerCode = `
  self.onmessage = function(e) {
    const result = e.data * e.data;
    self.postMessage({ resu

lt }); } `; const blob = new Blob([workerCode], { type: 'application/javascript' }); const worker = new Worker(URL.createObjectURL(blob)); worker.onmessage = (e) => console.log('平方结果:', e.data.result); worker.postMessage(123); // 触发计算 // 别忘了清理 return () => URL.revokeObjectURL(blob);

主线程与 Worker 通信只能靠 postMessage + onmessage

没有共享内存(除非用 SharedArrayBuffer,但需开启跨域策略且兼容性差),所有数据都经序列化/反序列化传递。这意味着:

  • 传对象时,函数、undefined、Symbol、Date 对象、RegExp 等会被丢弃或转成空对象
  • 大数据量(如百万级数组)会有明显序列化开销,不是“零成本”通信
  • 想传 ArrayBuffer 或 TypedArray 可用 transferable 选项避免拷贝,例如 postMessage(data, [data.buffer])
  • 不能用 await 直接等 Worker 返回,必须用回调或封装成 Promise

Worker 中的错误不会冒泡到主线程,必须手动监听

Worker 内抛出未捕获异常,只会静默失败,主线程完全感知不到。常见陷阱是:Worker 里 fetch 报错、JSON 解析失败、循环卡死,结果主线程一直等 onmessage 却收不到响应。

  • 务必在 Worker 内加 self.onerrortry/catch 包裹主逻辑
  • 主线程也建议设 worker.onerror,但注意它只捕获初始化阶段错误(如脚本加载失败),运行时错误得靠 postMessage 主动上报
  • 复杂任务建议加超时机制,比如主线程用 setTimeout 检测是否响应超时

多线程不是银弹。Worker 启动有开销,频繁创建销毁反而比同步更慢;真正适合的是计算密集型任务(加密、图像处理、解析大 JSON)、可拆分的批量处理,而不是简单地把几个 for 循环塞进去。UI 是否卡顿,关键看主线程有没有长时间执行的 JS,而不在“用了多少线程”。