关于HTML5中的WebWorker
1.javascript的单线程问题
众所周知,js是不擅长计算的。因为计算是同步的,大规模的计算会让js主线程阻塞,主线程阻塞的结果就和界面完全卡死一样。
虽然javascript引入了异步概念,但异步只是把任务发布出去等着,后面还是会拉到主线程执行的,异步不可能在异步队列自己执行。所以一个耗时很高的操作,不管做不做异步,始终会卡死页面。下面是异步处理耗时高的操作的一个示例图:

2.WebWorker是什么?
为了给JavaScript 创造多线程环境,允许主线程创建 Worker线程,将一些任务分配给后者运行。

3.注意点
- 同源限制
分配给Worker线程运行的脚本文件,必须与主线程的脚本文件同源。 - DOM 限制
Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的DOM对象,也无法使用document、window、parent这些对象。但是,Worker线程可以navigator对象和location对象。 - 通信联系
Worker线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息完成。 - 脚本限制
Worker线程不能执行alert()方法和confirm()方法,但可以使用XMLHttpRequest对象发出AJAX请求 - 文件限制
Worker线程无法读取本地文件,即不能打开本机的文件系统(file://),它所加载的脚本,必须来自网络。
4.基本使用
下面使用Vue3作为实例。
base-webwoker.vue:
<template>
<span>webworker基本使用</span>
<button @click="sendMessage">发消息给工人</button>
</template>
<script setup>
let worker1 = new Worker("http://localhost:5173/list.js");
worker1.addEventListener("message", (e) => {
console.log(e.data); //2
});
const sendMessage = () => {
worker1.postMessage("你好");
};
</script>
<style scoped></style>
注意为了做测试list文件必须写在public文件夹里面,才能保证同源。后面上线可以将相关webworker文件以cdn的形式提供,重要保证同源即可。

list.js:
let a = 1 + 1;
self.postMessage(a);
self.addEventListener("message", (e) => {
console.log(e.data);
self.postMessage("收到");
});
然后运行项目,控制台一开始就会打印2:

点击发消息给工人按钮:

有些东西无法通过主线程传递给子线程。比如方法、dom节点、一些对象的特殊设置(freeze、getter、setter这些,所以vue的响应式对象是不能传的)。


再说说模块的引入问题:
在public文件夹新建a.js:
function a() {
console.log("test");
}
我们想要在list.js中引入这个模块,这时候是不能通过import引入的:


需要用到importScripts:


这在引入一些第三方库CDN时候可以很有用,特别是那些比较旧的库,它们没有相应的模块化规范。
如果引入的文件是esm规范,这时候必须在new Worker()时候必须要说明:
export function test() {
console.log("test");
}



5.应用场景
由于webworker在dom方法的限制,所以不能使用webworker进行多线程渲染(所以vue、react框架没有考虑用webworker)。webworker的常见应用是处理耗时计算操作。
5.1 在线滤镜
随着webgl、canvas等的流行,前端有越来越多的可视化操作。如在线滤镜、在线绘图、web游戏等。这些东西十分消耗计算。
如下filter-image.vue
<template>
<input />
<button @click="handleImg">过滤</button>
<canvas id="img-canvas" width="1200px" height="600px"></canvas>
</template>
<script setup>
import imageResource from "../assets/qd.jpg";
let canvas;
let ctx;
let img = new Image();
img.src = imageResource;
img.onload = function () {
canvas = document.getElementById("img-canvas");
ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, 1200, 600);
};
function handleImg() {
//读取所有像素点
const imageData = ctx.getImageData(0, 0, 1200, 600);
console.log(imageData);
const data = imageData.data;
//循环每个像素点
for (let i = 0; i < imageData.data.length; i++) {
for (let j = 0; j < 1000; j++) {
if (imageData.data[i] != 255) {
imageData.data[i] = Math.min(data[i] + j, 0);
}
}
}
ctx.putImageData(imageData, 0, 0);
}
</script>
<style scoped></style>
运行项目:

点击过滤按钮:

该操作需要2819ms,也就是2s。在点击过滤按钮之后立马在input中输入,在那2s多的时间内是无法输入的,因为该操作被阻塞了。这时候我们需要用到webworker:
新建picworker.js文件:
self.addEventListener("message", (e) => {
if (e.data.data.length) {
let imageData = e.data;
//循环每个像素点
for (let i = 0; i < imageData.data.length; i++) {
for (let j = 0; j < 1000; j++) {
if (imageData.data[i] != 255) {
imageData.data[i] = Math.min(imageData.data[i] + j, 0);
}
}
}
self.postMessage(imageData);
}
});
修改filter-image.vue:
<script setup>
import imageResource from "../assets/qd.jpg";
let canvas;
let ctx;
let img = new Image();
img.src = imageResource;
img.onload = function () {
canvas = document.getElementById("img-canvas");
ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, 1200, 600);
};
let worker1 = new Worker("http://localhost:5173/picworker.js");
worker1.addEventListener("message", (e) => {
const imageData = e.data;
ctx.putImageData(imageData, 0, 0);
});
function handleImg() {
//读取所有像素点
const imageData = ctx.getImageData(0, 0, 1200, 600);
worker1.postMessage(imageData);
}
</script>
这样子就解决了阻塞问题。
5.2 处理巨量数据电子表单
有些后台管理系统会涉及到电子表单数据的大量计算,如10万条数据导出为excel表格。
安装xlsx:
npm i xlsx
如下excel-counter.vue
<template>
<input />
<button @click="exportExcel">导出</button>
</template>
<script setup>
import { utils, writeFile } from "xlsx";
let arr = [];
for (let i = 0; i < 100000; i++) {
arr.push({
id: i,
name: `张三${i}号`,
location: `xxx大道${i}号`,
age: i,
a: i * 2,
b: i / 2,
c: 1 + 2,
d: 23,
e: 111,
f: 123,
});
}
function exportExcel() {
// 创建一个新的工作簿
const workbook = utils.book_new();
// 创建一个工作表 要求一个对象数组格式
const sheet = utils.json_to_sheet(arr);
// 把工作表添加到工作簿 Data为工作表名称
utils.book_append_sheet(workbook, sheet, "Sheet1");
writeFile(workbook, "test.xlsx");
}
</script>
<style scoped></style>
运行项目:

点击导出按钮,会发现导出这个Excel表格用了2s多:

在点击导出按钮之后立马在input中输入,在那2s多的时间内是无法输入的,因为该操作被阻塞了。这时候我们需要用到webworker:
先去node_modules中找到xlsx.js文件:

将其复制到public文件夹,然后新建public/excelworker.js文件:
importScripts("./xlsx.js");
console.log(XLSX);
let arr = [];
for (let i = 0; i < 100000; i++) {
arr.push({
id: i,
name: `张三${i}号`,
location: `xxx大道${i}号`,
age: i,
a: i * 2,
b: i / 2,
c: 1 + 2,
d: 23,
e: 111,
f: 123,
});
}
self.addEventListener("message", (e) => {
// 创建一个新的工作簿
const workbook = XLSX.utils.book_new();
// 创建一个工作表 要求一个对象数组格式
const sheet = XLSX.utils.json_to_sheet(arr);
// 把工作表添加到工作簿 Data为工作表名称
XLSX.utils.book_append_sheet(workbook, sheet, "Sheet1");
self.postMessage(workbook);
});
修改excel-counter.vue:
<template>
<input />
<button @click="exportExcel">导出</button>
</template>
<script setup>
import { writeFile } from "xlsx";
let worker1 = new Worker("http://localhost:5173/excelworker.js");
worker1.addEventListener("message", (e) => {
const workbook = e.data;
writeFile(workbook,"test.xlsx")
});
function exportExcel() {
worker1.postMessage("")
}
</script>
<style scoped></style>
这样子就解决了这个阻塞问题。

155

被折叠的 条评论
为什么被折叠?



