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 内部没有 windowdocument 对象,全局对象是 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 写法,非常适合您当前的文件上传组件开发场景。

相关推荐
ywf12151 小时前
前端的dist包放到后端springboot项目下一起打包
前端·spring boot·后端
恋猫de小郭1 小时前
2026,Android Compose 终于支持 Hot Reload 了,但是收费
android·前端·flutter
hpoenixf7 小时前
2026 年前端面试问什么
前端·面试
还是大剑师兰特7 小时前
Vue3 中的 defineExpose 完全指南
前端·javascript·vue.js
泯泷7 小时前
阶段一:从 0 看懂 JSVMP 架构,先在脑子里搭出一台最小 JSVM
前端·javascript·架构
mengchanmian8 小时前
前端node常用配置
前端
华洛8 小时前
利好打工人,openclaw不是企业提效工具,而是个人助理
前端·javascript·产品经理
xkxnq9 小时前
第六阶段:Vue生态高级整合与优化(第93天)Element Plus进阶:自定义主题(变量覆盖)+ 全局配置与组件按需加载优化
前端·javascript·vue.js
A黄俊辉A9 小时前
vue css中 :global的使用
前端·javascript·vue.js
小码哥_常10 小时前
被EdgeToEdge适配折磨疯了,谁懂!
前端