web系列之Web Worker

838 阅读2分钟

1. 什么是Web Worker

MDN-worker

  • Web Worker是一种在浏览器中运行的JavaScript线程,能够独立于主线程运行。
  • Web Worker可以加速Web应用的响应速度和处理能力。

2. Web Worker的使用场景

-   处理大量计算密集型任务,如图像处理、音视频编码、多线程下载等。
-   后台执行长时间运行的任务,如复杂的数据处理和分析。

3. 如何使用Web Worker

3.1 方法说明

  1. 开启worker
// Worker 构造函数
const worker = new Worker('worker.js')

  1. 监听数据
// 写法 1
self.addEventListener('message', function (e) {})

// 写法 2
this.addEventListener('message', function (e) {})

// 写法 3
addEventListener('message', function (e) {})

// 写法 4
onmessage = function (e) {}

3. 错误监听

// 主线程
worker.onerror = function () {}

// 主线程使用专用线程
worker.onmessageerror = function () {}

// 主线程使用共享线程
worker.port.onmessageerror = function () {}

// worker 线程
onerror = function () {}

4. 数据传递

// 主线程
worker.postMessage([10, 24])

// 子线程
postMessage(data)

5. 关闭worker

// 主线程
worker.terminate()

// Dedicated Worker 线程中  
self.close()

3.2 原生使用案例

使用vue3进行演示,当表格数据很大的情况,数据处理速度会慢,定义getList函数来获取数据。在getList中,将数据传递给handleWorker函数进行处理。

handleWorker函数中:

  • 首先判断浏览器是否支持Web Worker,如果支持,则创建一个Worker,并向Worker发送数据。
  • Worker会在后台进行数据处理,并将处理结果返回给主线程。
  • 主线程可以通过onmessage事件监听Worker返回的消息,并将消息中包含的数据赋值给tableData变量。这样,只要tableData变量发生变化,Vue组件就会自动更新视图。

在vue文件中创建Webworker.vue,将worker.js放在public下

Webworker.vue:

<template>
    <div>
        <el-table border :data="tableData" style="width: 100%; margin-top: 30px">
            <el-table-column prop="id" label="id" />
            <el-table-column prop="name" label="名字" />
            <el-table-column prop="time" label="时间" />
            <el-table-column prop="date" label="日期" />
        </el-table>
    </div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';

const tableData =ref<any[]>([])
const getList = async () => {	
    const res = await fetch('http://localhost:5173/main/getList').then(res => res.json())
    handleWorker(res.data)
}


const handleWorker = (data) => {
    if (window.Worker) {
            console.log('存在worker');
            let worker = new Worker('worker.js')
            worker.postMessage(data)
            worker.onmessage = function (e) {
                    console.log('打印***接受worker的信息', e.data)
                    tableData.value = e.data
            }
            worker.onmessageerror = function (err) {
                    console.log('error',err);

            }

    } else {
            console.log('没有worker');

    }
}

onMounted(()=>{
    getList()

})
</script>

worker.js

onmessage = function (e) {
	console.log("打印***接受主进程信息", e.data);
	console.time('worker')
	const handleData = e.data.map(item => {
		if (item.id !== 7) {
			return item
		}
		item.sex = 'man'
		item.pid = item.id
		item.key = item.id
		return item
	})
	console.timeEnd("worker");

	postMessage(handleData)
        self.close()
};


3.3 使用Vueuse中useWebWorker、useWebWorkerFn

将上面的代码进行改造,

  • useWebWorker将handleWorker使用post进行替换,数据返回使用watch进行监听即可
  • useWebWorkerFn更加高级,它将Web Worker的实现和数据处理的过程都封装起来,只提供一个数据处理函数
<template>
    <div>
        <el-table border :data="tableData" style="width: 100%; margin-top: 30px">
            <el-table-column prop="id" label="id" />
            <el-table-column prop="name" label="名字" />
            <el-table-column prop="time" label="时间" />
            <el-table-column prop="date" label="日期" />
        </el-table>
    </div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { useWebWorkerFn,useWebWorker } from '@vueuse/core'

// =======================useWebWorkerFn============================== 
/** 大型数组(500 万个数字)的排序 */
function heavyTask() {
  const randomNumber = () => Math.trunc(Math.random() * 5_000_00)
	const numbers: number[] = Array(5_000_000).fill(undefined).map(randomNumber)
	console.log('打印***numbers',numbers)
  numbers.sort()
  return numbers.slice(0, 5)
}

/** 
 * workerTerminate(status) 'PENDING' | 'SUCCESS' | 'RUNNING' | 'ERROR' | 'TIMEOUT_EXPIRED'
 * workerStatus 状态
 * workerFn
 */
const {  workerFn, workerStatus, workerTerminate } = useWebWorkerFn(heavyTask)

onMounted(async ()=>{
// res 为heavyTask的返回值
    const res = await workerFn()
    workerTerminate('PENDING') // 关闭
})


// =======================useWebWorker============================== 

const {data,post,terminate,worker} = useWebWorker('worker.js')


watch(() => data.value, (newData) => {
    console.log(newData, 'newData');
    terminate()
})

const tableData =ref<any[]>([])
const getList = async () => {	
    const res = await fetch('http://localhost:5173/main/getList').then(res => res.json())
    post(res.data)
}


onMounted(()=>{
    getList()
})
</script>

image.png

4. 总结

Web Worker的限制和注意事项

-   Web Worker不能直接访问DOM API,需要使用postMessage()方法和主线程进行通信。
-   利用Web Worker可能增加页面资源消耗,需要合理使用。
-   在某些浏览器中,Web Worker可能不支持所有ES6语法。

参考文章

JavaScript 性能利器 —— Web Worker