js中worker的详细讲解

什么是 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: '请求失败' });
      }
    })
  }
};

image

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>

image

现在我们使用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: '获取成功'
      }
    });
  }
};

image

关闭 Web Worker

在主线程和在Worker线程的关闭方式是不一样的。
如果在主线程,调用terminate()方法关闭

this.webWorker1 = new WorkerA('./web.worker.js') 
this.webWorker1.terminate();

如果是在Work线程,可以调用close()方法进行关闭

self.close();

主线程关闭 worker 线程的说明

当主线程调用worker.terminate()时,Worker线程会被立即终止。
无论当前事件循环中是否有任务,包括微任务和宏任务,都不会继续执行。
也就是说:如果我在点击[导入excel]按钮后,马上又关闭点击[关闭worker],那么不能够正常导出表格。
image

Worker线程内部关闭 worker 线程的说明

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

判断用户浏览器是否支持 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 内部的异常

posted @ 2026-01-04 09:03  南风晚来晚相识  阅读(134)  评论(0)    收藏  举报