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 写法,非常适合您当前的文件上传组件开发场景。

相关推荐
elangyipi1232 小时前
JavaScript 高级错误处理与 Chrome 调试艺术
开发语言·javascript·chrome
风舞红枫2 小时前
前端可配置权限规则案例
前端
前端不太难2 小时前
RN Navigation vs Vue Router:从架构底层到工程实践的深度对比
javascript·vue.js·架构
zhougl9962 小时前
前端模块化
前端
暴富暴富暴富啦啦啦2 小时前
Map 缓存和拿取
前端·javascript·缓存
天问一2 小时前
前端Vue使用js-audio-plugin实现录音功能
前端·javascript·vue.js
dodod20122 小时前
Ubuntu24.04.3执行sudo apt install yarnpkg 命令失败的原因
java·服务器·前端
小魏的马仔3 小时前
【elementui】el-date-picker日期选择框,获取焦点后宽度增加问题调整
前端·vue.js·elementui
JarvanMo3 小时前
想让你的 Flutter UI 更上一层楼吗?
前端