什么是 JavaScript Worker?
Worker 是在Web应用程序中实现多线程的机制。
它可以在一个单独的线程中运行脚本,独立于主线程。
这样,我们可以将一些耗时的计算任务交给Worker线程处理。
以保证主线程的不会被阻塞。
为什么需要Worker?
由于JavaScript是单线程的,所有任务在一个线程上执行。
如果遇到一个耗时的任务(比如大规模数据计算、图像处理、复杂算法)。
它会阻塞主线程导致页面无法响应,用户体验变差。
Worker的出现就是为了解决这个问题,将耗时的计算任务放到后台线程去执行。
vue2中如何使用 worker
在vue/cli脚手架中,我们无法直接使用worker。
因为:
1,无法将 Worker 脚本打包为独立的可加载文件。
2,打包后会导致依赖丢失或路径错误。
npm install worker-loader -D
或者
yarn add worker-loader -D
vue.config.js 配置如下
module.exports = {
chainWebpack:config=>{
// 下面是worker的配置
.rule('worker')
.test(/\.worker\.js$/)
.use('worker-loader')
.loader('worker-loader')
.end();
// 解决:worker 热更新问题
config.module.rule('js').exclude.add(/\.worker\.js$/);
},
parallel: false,
chainWebpack: config => {
// 解决:"window is undefined"报错,这个是因为worker线程中不存在window对象,因此不能直接使用,要用this代替
config.output.globalObject('this')
}
}
postMessage 子线程给主线发送消息
self.postMessage子线程向主线程发送消息。
在 Web Worker 中,self 指向当前创建的这个 worker 线程,self 是一个全局对象。
self.postMessage()将数据从 worker 线程发送到创建它的主线程。
主线程通知子线程(worker)开始工作
// 创建 worker
this.webWorker = new Worker('./web.worker.js')
// 通知 Worker 子线程可以进行响应的工作。
// 主线程调用worker.postMessage()方法,向 Worker 发消息
this.webWorker.postMessage({ action: 'startFetch' });
worker 中来发送网络请求哈
src\views\element.vue 文件
<template>
<div class="page">
<h1>使用worker来发发送网络请求</h1>
<el-button @click="sendFetchHandler">开始发送网络请求</el-button>
<ul>
<li v-for="(item,index) in apiBackData" :key="index">{{ item.title }}</li>
</ul>
</div>
</template>
<script>
// 导入 worker 文件, 必须使用 这样的方式引入
import WorkerA from 'worker-loader!./web.worker.js'
export default {
data() {
return {
apiBackData: []
}
},
methods: {
sendFetchHandler(){
// 主线程调用worker.postMessage()方法,向 Worker线程 发消息,开始进行网络请求
this.webWorker1.postMessage({ action: 'startFetch' });
}
},
created() {
// 创建 worker 实例,名称必须和导入时的保持一致
this.webWorker1 = new WorkerA('./web.worker.js')
// 先添加事件监听器
this.webWorker1.addEventListener('message', (e) => {
// e.data 是接收到的数据
console.log('收到来自 Worker 的消息:', e.data)
// 处理 Worker 返回的数据
if (e.data.type === 'fetchSuccess') {
this.apiBackData = e.data.data.data || []
console.log('获取的数据:', this.apiBackData );
} else if (e.data.type === 'fetchError') {
console.log('获取数据失败:', e.data.error);
}
})
this.webWorker1.addEventListener('error', (error) => {
console.error('Worker发生错误:', error)
})
}
}
</script>
// 引入网络请求中的方法
import {getList} from '@/request/api.js'
self.onmessage = function(e) {
console.log("worker:", e)
if(e.data && e.data.action==='startFetch'){
// e.data是主线程发送过来的数据
getList().then(res=>{
console.log(1111,res)
// 假设 是一个数组就是请求成功
if(Array.isArray(res)){
//将数据从 worker 线程发送到创建它的主线程
self.postMessage({
type: 'fetchSuccess',
data:{
data:res,
message: '获取成功'
}
});
}else{
// 将数据从 worker 线程发送到创建它的主线程
self.postMessage({ type: 'fetchError', error: '请求失败' });
}
})
}
};

worker导出表格案例
<template>
<div class="page">
<input type="text">
<br/>
<el-button @click="exportExcelHandler">导出excel</el-button>
</div>
</template>
<script>
import {writeFile, utils} from 'xlsx'
export default {
data() {
return {
}
},
methods: {
exportExcelHandler(){
let arr = []
// 50w行的数据
for(let i= 0; i<500000;i++){
arr.push({
name: '张' + i,
age: 18,
sex: '男',
id: '2025_12_12'+ i,
address:'XX区YYY大道' + i + '号'
})
}
// 将对象数组 arr 转换为 Excel 工作表(sheet)
const sheet = utils.json_to_sheet(arr)
// 创建一个新的空工作簿对象。
// 工作簿是 Excel 文件的容器,可以包含多个工作表
const workbook = utils.book_new()
// 将之前创建的工作表 sheet 添加到工作簿 workbook 中
utils.book_append_sheet(workbook, sheet, 'sheet1')
console.log('workbook', workbook)
// 将整个工作簿 workbook 写入文件并触发浏览器下载
writeFile(workbook, 'test.xlsx')
// 主线程调用worker.postMessage()方法,向 Worker线程 发消息,开始进行网络请求
// this.webWorker1.postMessage({ action: 'startFetch', data: this.vueData });
}
}
}
</script>

现在我们使用worker来解决添加数据耗时的问题
下载xlsx模块。
npm install --save xlsx
使用worker的文件(下载的主页面)
<template>
<div class="page">
<input type="text">
<br/>
<el-button @click="exportExcelHandler">导出excel</el-button>
</div>
</template>
<script>
// 导入 worker 文件, 必须使用 这样的方式引入
import WorkerA from 'worker-loader!./web.worker.js'
import {writeFile} from 'xlsx'
export default {
data() {
return {
}
},
methods: {
exportExcelHandler(){
// 主线程调用worker.postMessage()方法,向 Worker线程 发消息,开始进行构造数据
this.webWorker1.postMessage({ action: 'startCreateExcelData'});
}
},
created() {
// 创建 worker 实例,名称必须和导入时的保持一致
this.webWorker1 = new WorkerA('./web.worker.js')
// 先添加事件监听器
this.webWorker1.addEventListener('message', (e) => {
console.log('收到来自 Worker 的消息:', e.data)
// 处理 Worker 返回的数据
if (e.data.type === 'createExcelSuccess') {
// 返回表的数据
const workbook = e.data.data.data
// 将整个工作簿 workbook 写入文件并触发浏览器下载。
// 最后异步仍然会卡顿,因为数据量太大了.但是卡顿的时间会比之前好一些
writeFile(workbook, '通过worker导处excel.xlsx')
} else {
console.log('获取数据失败:', e.data.error);
}
})
this.webWorker1.addEventListener('error', (error) => {
console.error('Worker发生错误:', error)
})
}
}
</script>
worker文件(src\views\web.worker.js )
// 引入 xlsx
import {utils} from 'xlsx'
self.onmessage = function(e) {
console.log("worker:", e)
if(e.data && e.data.action==='startCreateExcelData'){
// e.data是主线程发送过来的数据
let arr = []
// 50w行的数据
for(let i= 0; i<500000;i++){
arr.push({
name: '张' + i,
age: 18,
sex: '男',
id: '2025_12_12'+ i,
address:'XX区YYY大道' + i + '号'
})
}
// 将对象数组 arr 转换为 Excel 工作表(sheet)
const sheet = utils.json_to_sheet(arr)
// 创建一个新的空工作簿(book)对象。工作簿是 Excel 文件的容器,可以包含多个工作表(sheet)
const workbook = utils.book_new()
// 将之前创建的工作表 (sheet) 添加到工作簿(workbook)中
utils.book_append_sheet(workbook, sheet, 'sheet1')
self.postMessage({
type: 'createExcelSuccess',
data:{
data:workbook,
message: '获取成功'
}
});
}
};

关闭 Web Worker
在主线程和在Worker线程的关闭方式是不一样的。
如果在主线程,调用terminate()方法关闭
this.webWorker1 = new WorkerA('./web.worker.js')
this.webWorker1.terminate();
如果是在Work线程,可以调用close()方法进行关闭
self.close();
主线程关闭 worker 线程的说明
当主线程调用worker.terminate()时,Worker线程会被立即终止。
无论当前事件循环中是否有任务,包括微任务和宏任务,都不会继续执行。
也就是说:如果我在点击[导入excel]按钮后,马上又关闭点击[关闭worker],那么不能够正常导出表格。

Worker线程内部关闭 worker 线程的说明
当Worker线程内部调用self.close()时,Worker线程并不会立即终止。
它会[继续执行当前]事件循环中的任务,但是[会阻止后续]事件循环的任务执行。
也就是说:如果我在点击[导入excel]按钮后,马上又关闭点击[Worker内部关闭Worker],仍然可以导出表格。

判断用户浏览器是否支持 worker
if (typeof Worker !== 'undefined') {
console.log('浏览器支持 Web Workers');
} else {
console.log('浏览器不支持 Web Workers');
}
Worker 线程引用其他js文件
有些时候,我们需要在worker文件中,引入其他模块的文件。
可以通过 importScripts() 方法来实现,在 worker 中导入其他的模块。这个地址是可以跨域的。
Worker 指定模块类型
如果我们发现我们需要导出的方法使用的是ESModule 模式导出。
此时使用 importScripts() 方法引入他模块,会报错。
此时需要指定模块类型。
// main.js(主线程的代码)
const worker = new Worker('/worker.js', {
type: 'module' // 指定 worker.js 的类型
});
// worker.js(worker线程的代码)
import add from './utils.js'; // 导入外部js
self.addEventListener('message', e => {
postMessage(e.data);
});
add(1, 2);
// utils.js 使用了ESModule 模块导出的 add 方法
export default add = (a, b) => a + b;
使用worker的注意事项
1,worker不能使用window上的dom操作,也不能获取dom对象,dom相关的东西只有主线程有。worker只能做一些计算相关的操作
2,有的东西是无法通过主线程传递个子线程的。
比如:方法,dom节点,一些对象里的特殊设置(freeze,getter,setter这些)所以vue的响应式对象在传递之后就会变成普通对象
worker实际应用案例
1,将语法检查、代码压缩等任务放在 Worker 中执行
2,图像处理:比如使用Canvas处理图片,进行滤镜
worker性能优化建议
减少通信开销:避免频繁传递大量数据,必要时使用 Transferable Objects(如 ArrayBuffer)
批量处理:对于大批量任务,可批量创建 Worker 并行处理
常驻线程:对于频繁使用的 Worker,可保持常驻而非重复创建
错误处理:始终监听 onerror 事件,捕获 Worker 内部的异常