Web Worker

1. 什么是 Web Worker?

JavaScript 默认是单线程 的(主线程)。这意味着它一次只能做一件事:要么处理页面渲染(UI),要么处理逻辑计算(JS)。如果计算任务太重(如处理几千个文件、复杂的数学运算),主线程就会被阻塞,导致页面卡顿、假死。

Web Worker 的作用是:允许你在后台开启一个新的独立线程。

  • 主线程:专心负责界面显示、用户点击交互。

  • Worker 线程:在后台默默处理耗时的"脏活累活"。


2. 核心通信机制

主线程和 Worker 线程就像两个完全独立的房间,它们不能直接共享变量,只能通过 "发消息" 来沟通。

动作 主线程 (Vue 组件) Worker 线程 (后台脚本)
发送数据 worker.postMessage(data) self.postMessage(result)
接收数据 worker.onmessage = (e) => {} self.onmessage = (e) => {}
销毁线程 worker.terminate() self.close()
错误监听 worker.onerror = (e) => {} -

3. 标准使用方式(外链文件法)

这是最标准的使用方式,适用于 Worker 逻辑非常复杂,需要独立成一个 .js 文件的情况。

第一步:创建 worker.js (在 public 目录下)

注意:Worker 内部没有 window 或 document 对象,全局对象是 self。

复制代码
// public/worker.js

// 1. 监听主线程发来的消息
self.onmessage = function(e) {
  console.log('Worker: 收到主线程任务', e.data);

  const { num1, num2 } = e.data;
  
  // 2. 执行耗时计算
  const result = num1 + num2;

  // 3. 把结果发回给主线程
  self.postMessage(result);
};

第二步:在主线程中调用 (main.js)

复制代码
// main.js

// 1. 创建 Worker 实例
const myWorker = new Worker('/worker.js');

// 2. 发送数据给 Worker
myWorker.postMessage({ num1: 100, num2: 200 });

// 3. 接收 Worker 的处理结果
myWorker.onmessage = function(e) {
  console.log('主线程: 收到计算结果', e.data); // 输出 300
  
  // 4. 任务完成,关闭线程(节省资源)
  myWorker.terminate();
};

4. Vue 3 高级实战:内联写法 (Blob URL)

在 Vue 组件开发中,为了避免创建额外的外部 JS 文件,我们通常使用 Blob URL 技术,把 Worker 代码直接写在 Vue 组件里。这也正是您之前代码中使用的方法。

场景实例:计算斐波那契数列(CPU 密集型任务)

如果我们直接在主线程算 fib(40),页面会卡死几秒钟。用 Worker 则如丝般顺滑。

复制代码
<template>
  <el-card>
    <h3>Web Worker 斐波那契计算器</h3>
    <div style="margin: 20px 0;">
      <el-input-number v-model="num" :min="1" :max="50" />
      <el-button type="primary" @click="startCalculation" :loading="calculating">
        开始计算
      </el-button>
      <el-button type="danger" @click="stopWorker" :disabled="!calculating">
        强制停止
      </el-button>
    </div>
    <div v-if="result !== null">
      计算结果: <span style="color: #409eff; font-weight: bold;">{{ result }}</span>
    </div>
    <div style="margin-top: 10px; color: #909399; font-size: 12px;">
      提示:试着在计算时拖动窗口或点击按钮,你会发现页面依然流畅!
    </div>
  </el-card>
</template>

<script setup>
import { ref, onBeforeUnmount } from 'vue';

const num = ref(40);
const result = ref(null);
const calculating = ref(false);
let workerInstance = null;

// --- 1. 定义 Worker 内部代码 (字符串形式) ---
const workerScript = `
  // 斐波那契递归函数 (非常耗时)
  function fib(n) {
    if (n <= 1) return n;
    return fib(n - 1) + fib(n - 2);
  }

  self.onmessage = function(e) {
    const start = Date.now();
    const n = e.data;
    
    // 开始计算
    const res = fib(n);
    const time = Date.now() - start;
    
    // 发回结果
    self.postMessage({ status: 'success', result: res, time });
  };
`;

const startCalculation = () => {
  result.value = null;
  calculating.value = true;

  // --- 2. 动态创建 Worker ---
  // 将字符串转换为 Blob 对象
  const blob = new Blob([workerScript], { type: 'application/javascript' });
  // 生成临时的 Blob URL (例如: blob:http://localhost/...)
  const workerUrl = URL.createObjectURL(blob);
  
  // 初始化 Worker
  workerInstance = new Worker(workerUrl);

  // --- 3. 发送任务 ---
  workerInstance.postMessage(num.value);

  // --- 4. 接收结果 ---
  workerInstance.onmessage = (e) => {
    const { result: res, time } = e.data;
    result.value = `${res} (耗时 ${time}ms)`;
    terminateWorker(); // 算完就销毁
  };

  workerInstance.onerror = (err) => {
    console.error('Worker 报错:', err);
    terminateWorker();
  };
};

// 停止/销毁 Worker
const stopWorker = () => {
  if (workerInstance) {
    workerInstance.terminate(); // 立即杀死线程
    result.value = '计算已人为终止';
    terminateWorker();
  }
};

// 清理函数
const terminateWorker = () => {
  calculating.value = false;
  workerInstance = null;
};

// 组件销毁时务必清理
onBeforeUnmount(() => {
  if (workerInstance) workerInstance.terminate();
});
</script>

5. 关键注意事项(避坑指南)

1. 作用域限制 (No DOM)

Worker 线程不能操作 DOM。

  • ❌ document.getElementById('app') (报错)

  • ❌ window.alert('Hello') (报错)

  • ❌ Vue.ref (无法访问主线程的 Vue 响应式变量)

  • ✅ setTimeout, setInterval, XMLHttpRequest, fetch (这些可以用)

2. 数据拷贝开销

主线程传数据给 Worker 时,数据会被拷贝(Structured Clone)。

  • 如果是普通 JSON 对象,速度很快。

  • 如果是巨大的数据(如 500MB 的文件),拷贝会消耗内存。

  • 优化 :对于二进制数据(ArrayBuffer),可以使用 Transferable Objects(所有权转移),实现"零拷贝"传输,速度极快。

    复制代码
    // 移交 buffer 的所有权给 worker,主线程将无法再访问该 buffer
    worker.postMessage(buffer, [buffer]); 

3. 同源策略

如果加载外部 .js 文件,该文件必须与主页面同源(协议、域名、端口一致),否则会报跨域错误。使用 Blob URL 内联写法 可以完美绕过这个问题。


6. 总结:什么时候该用它?

场景 是否推荐 Worker 理由
简单的表单验证 ❌ 不推荐 通信开销可能比计算本身还大。
几千个文件上传 ✅ 强烈推荐 计算 MD5、切片、并发请求都不会卡顿 UI。
Excel 导入导出 ✅ 强烈推荐 解析几万行数据是 CPU 密集型任务。
图像/视频处理 ✅ 强烈推荐 滤镜、压缩等操作非常耗时。
实时数据轮询 ⭕ 可以用 如果逻辑复杂,放在后台更稳定。

这份文档为您提供了从理论到 Vue 实战的完整路径,特别是内联 Blob 写法,非常适合您当前的文件上传组件开发场景。

相关推荐
卸任18 小时前
Vibe coding工具使用心得
前端·ai编程
brien919 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance19 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M19 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写19 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李19 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易20 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
清晨Coding20 小时前
删了 Cookie 还能自动登录?Google系网站登录态自动恢复的原理分析
javascript
风骏时光牛马20 小时前
前沿视界
前端
kyriewen20 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员