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