前端大数据处理 - Web Worker

前言

先了解一个概念:页面假死
浏览器有GUI渲染线程JS引擎线程,这两个线程是互斥的关系

当js有大量计算时,会造成 UI 阻塞,出现界面卡顿、掉帧等情况,严重时会出现页面卡死的情况,俗称假死

在前端开发中,处理大数据往往会导致主线程阻塞,从而影响页面的响应性能。这种情况下,可以使用 Web Worker,它运行在浏览器主线程之外,用于处理耗时任务,从而保持用户界面的流畅性。

介绍

Web Worker 是一种运行在后台的 JavaScript 脚本,不会影响主线程的性能。Web Worker 不能直接操作 DOM,但可以通过消息传递(postMessage 和 onmessage)与主线程通信。

关键特性
独立线程:运行在独立线程中,不会阻塞主线程。
消息通信:通过消息事件(postMessage 和 onmessage)与主线程交互。

受限环境

1、在 Worker 线程的运行环境中没有 window 全局对象,也无法访问 DOM 对象
2、Worker中只能获取到部分浏览器提供的 API,如定时器、fetch、navigator、location、XMLHttpRequest等。
3、由于可以获取XMLHttpRequest 对象,可以在 Worker 线程中执行ajax请求
4、每个线程运行在完全独立的环境中,需要通过postMessage、 message事件机制来实现的线程之间的通信

适用场景

大数据处理:复杂的计算、排序、筛选等操作。
文件操作:大文件的分片处理、解析。
图像处理:复杂的图像过滤或编辑。
网络请求:异步批量请求处理。

使用

假如有一个大数据数组排序

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Web Worker Example</title>
</head>
<body><h1>Web Worker 大数据排序</h1><button id="sortButton">开始排序</button><div id="result"></div><script src="main.js"></script>
</body>
</html>

1、创建一个独立的 Worker 脚本文件

// worker.js
// 接收主线程的数据
self.onmessage = function (event) {const { data } = event;if (data.type === "sort") {const sortedArray = data.array.sort((a, b) => a - b);// 将结果发送回主线程self.postMessage({ type: "result", sortedArray });}
};

2、创建 Web Worker

// main.js// 创建 Web Worker 实例
const worker = new Worker("worker.js");document.getElementById("sortButton").addEventListener("click", () => {const largeArray = Array.from({ length: 1000000 }, () =>Math.floor(Math.random() * 100000));console.log("开始排序...");const startTime = performance.now();// 向 Worker 发送数据worker.postMessage({ type: "sort", array: largeArray });// 接收 Worker 返回的结果worker.onmessage = function (event) {const { data } = event;if (data.type === "result") {const endTime = performance.now();console.log("排序完成:", data.sortedArray);document.getElementById("result").innerText = `排序完成,用时:${(endTime - startTime).toFixed(2)} ms`;}};
});
示例说明
主线程(main.js):
创建了一个包含 1,000,000 个随机数的数组。
使用 worker.postMessage 将数据传递到 Worker。
接收 worker.onmessage 返回的排序结果。
Worker(worker.js):接收到数据后进行排序。
排序完成后通过 self.postMessage 将结果返回主线程。
结果展示:点击按钮后,页面不会因为排序操作卡顿,用户仍然可以正常操作页面。

在这里插入图片描述
注意:

直接通过浏览器打开 index.html 时,浏览器会因同源策略的限制,禁止加载文件协议(file://)下的 Web Worker 文件。
使用 npx 运行一个临时服务器:
打开文件所在终端:npx serve
serve 是一个轻量级静态文件服务器,会默认监听 http://localhost:3000。

在Vue中 使用 Web Worker

1、安装worker-loader

npm install worker-loader

2、编写worker.js

onmessage = function (e) {// onmessage获取传入的初始值let sum = e.data;for (let i = 0; i < 200000; i++) {for (let i = 0; i < 10000; i++) {sum += Math.random()}}// 将计算的结果传递出去postMessage(sum);
}

3、通过行内loader 引入 worker.js

import Worker from "worker-loader!./worker"
<template><div><button @click="makeWorker">开始线程</button><!--在计算时 往input输入值时 没有发生卡顿--><p><input type="text"></p></div>
</template><script>import Worker from "worker-loader!./worker";export default {methods: {makeWorker() {// 获取计算开始的时间let start = performance.now();// 新建一个线程let worker = new Worker();// 线程之间通过postMessage进行通信worker.postMessage(0);// 监听message事件worker.addEventListener("message", (e) => {// 关闭线程worker.terminate();// 获取计算结束的时间let end = performance.now();// 得到总的计算时间let durationTime = end - start;console.log('计算结果:', e.data);console.log(`代码执行了 ${durationTime} 毫秒`);});}},}
</script>
input框的作用是:我们可以在计算过程中,在input框输入值,页面一直未发生卡顿对比试验
如果直接把下面这段代码直接丢到主线程中,计算过程中页面一直处于假死状态,input框无法输入
let sum = 0;
for (let i = 0; i < 200000; i++) {for (let i = 0; i < 10000; i++) {sum += Math.random()}}

开启多线程,并行计算

场景:如果大量数据,并且有多种运算,怎么做?
处理:给每种运算开启单独的线程,线程计算完成后要及时关闭

多线程代码

<template><div><button @click="makeWorker">开始线程</button><!--在计算时 往input输入值时 没有发生卡顿--><p><input type="text"></p></div>
</template><script>import Worker from "worker-loader!./worker";export default {data() {// 模拟数据let arr = new Array(100000).fill(1).map(() => Math.random()* 10000);let weightedList = new Array(100000).fill(1).map(() => Math.random()* 10000);let calcList = [{type: 'sum', name: '总和'},{type: 'average', name: '算术平均'},{type: 'weightedAverage', name: '加权平均'},{type: 'max', name: '最大'},{type: 'middleNum', name: '中位数'},{type: 'min', name: '最小'},{type: 'variance', name: '样本方差'},{type: 'popVariance', name: '总体方差'},{type: 'stdDeviation', name: '样本标准差'},{type: 'popStandardDeviation', name: '总体标准差'}]return {workerList: [], // 用来存储所有的线程calcList, // 计算类型arr, // 数据weightedList // 加权因子}},methods: {makeWorker() {this.calcList.forEach(item => {let workerName = `worker${this.workerList.length}`;let worker = new Worker();let start = performance.now();worker.postMessage({arr: this.arr, type: item.type, weightedList: this.weightedList});worker.addEventListener("message", (e) => {worker.terminate();let tastName = '';this.calcList.forEach(item => {if(item.type === e.data.type) {item.value = e.data.value;tastName = item.name;}})let end = performance.now();let duration = end - start;console.log(`当前任务: ${tastName}, 计算用时: ${duration} 毫秒`);});this.workerList.push({ [workerName]: worker });})},clearWorker() {if (this.workerList.length > 0) {this.workerList.forEach((item, key) => {item[`worker${key}`].terminate && item[`worker${key}`].terminate(); // 终止所有线程});}}},// 页面关闭,如果还没有计算完成,要销毁对应线程beforeDestroy() {this.clearWorker();},}
</script>

worker.js

import { create, all } from 'mathjs'
const config = {number: 'BigNumber',precision: 20 // 精度
}
const math = create(all, config);//加
const numberAdd = (arg1,arg2) => {return math.number(math.add(math.bignumber(arg1), math.bignumber(arg2)));
}
//减
const numberSub = (arg1,arg2) => {return math.number(math.subtract(math.bignumber(arg1), math.bignumber(arg2)));
}
//乘
const numberMultiply = (arg1, arg2) => {return math.number(math.multiply(math.bignumber(arg1), math.bignumber(arg2)));
}
//除
const numberDivide = (arg1, arg2) => {return math.number(math.divide(math.bignumber(arg1), math.bignumber(arg2)));
}
// 数组总体标准差公式
const popVariance = (arr) => {return Math.sqrt(popStandardDeviation(arr))
}// 数组总体方差公式
const popStandardDeviation = (arr) => {let s,ave,sum = 0,sums= 0,len = arr.length;for (let i = 0; i < len; i++) {sum = numberAdd(Number(arr[i]), sum);}ave = numberDivide(sum, len);for(let i = 0; i < len; i++) {sums = numberAdd(sums, numberMultiply(numberSub(Number(arr[i]), ave), numberSub(Number(arr[i]), ave)))}s = numberDivide(sums,len)return s;
}// 数组加权公式
const weightedAverage = (arr1, arr2) => { // arr1: 计算列,arr2: 选择的权重列let s,sum = 0, // 分子的值sums= 0, // 分母的值len = arr1.length;for (let i = 0; i < len; i++) {sum = numberAdd(numberMultiply(Number(arr1[i]), Number(arr2[i])), sum);sums = numberAdd(Number(arr2[i]), sums);}s = numberDivide(sum,sums)return s;
}// 数组样本方差公式
const variance = (arr) => {let s,ave,sum = 0,sums= 0,len = arr.length;for (let i = 0; i < len; i++) {sum = numberAdd(Number(arr[i]), sum);}ave = numberDivide(sum, len);for(let i = 0; i < len; i++) {sums = numberAdd(sums, numberMultiply(numberSub(Number(arr[i]), ave), numberSub(Number(arr[i]), ave)))}s = numberDivide(sums,(len-1))return s;
}
// 数组中位数
const middleNum = (arr) => {arr.sort((a,b) => a - b)if(arr.length%2 === 0){ //判断数字个数是奇数还是偶数return numberDivide(numberAdd(arr[arr.length/2-1], arr[arr.length/2]),2);//偶数个取中间两个数的平均数}else{return arr[(arr.length+1)/2-1];//奇数个取最中间那个数}
}// 数组求和
const sum = (arr) => {let sum = 0, len = arr.length;for (let i = 0; i < len; i++) {sum = numberAdd(Number(arr[i]), sum);}return sum;
}// 数组平均值
const average = (arr) => {return numberDivide(sum(arr), arr.length)
}
// 数组最大值
const max = (arr) => {let max = arr[0]for (let i = 0; i < arr.length; i++) {if(max < arr[i]) {max = arr[i]}}return max
}// 数组最小值
const min = (arr) => {let min = arr[0]for (let i = 0; i < arr.length; i++) {if(min > arr[i]) {min = arr[i]}}return min
}
// 数组有效数据长度
const count = (arr) => {let remove = ['', ' ', null , undefined, '-']; // 排除无效的数据return arr.filter(item => !remove.includes(item)).length
}// 数组样本标准差公式
const stdDeviation = (arr) => {return Math.sqrt(variance(arr))
}// 数字三位加逗号,保留两位小数
const formatNumber = (num, pointNum = 2) => {if ((!num && num !== 0) || num == '-') return '--'let arr = (typeof num == 'string' ? parseFloat(num) : num).toFixed(pointNum).split('.')let intNum = arr[0].replace(/\d{1,3}(?=(\d{3})+(.\d*)?$)/g,'$&,')return arr[1] === undefined ? intNum : `${intNum}.${arr[1]}`
}
onmessage = function (e) {let {arr, type, weightedList} = e.datalet value = '';switch (type) {case 'sum':value = formatNumber(sum(arr));breakcase 'average':value = formatNumber(average(arr));breakcase 'weightedAverage':value = formatNumber(weightedAverage(arr, weightedList));breakcase 'max':value = formatNumber(max(arr));breakcase 'middleNum':value = formatNumber(middleNum(arr));breakcase 'min':value = formatNumber(min(arr));breakcase 'variance':value = formatNumber(variance(arr));breakcase 'popVariance':value = formatNumber(popVariance(arr));breakcase 'stdDeviation':value = formatNumber(stdDeviation(arr));breakcase 'popStandardDeviation':value = formatNumber(popStandardDeviation(arr));break}// 发送数据事件postMessage({type, value});
}

在这里插入图片描述

web worker 提高Canvas运行速度

web worker除了计算外,还可以结合离屏canvas进行绘图,提升绘图的渲染性能和使用体验

canvas案例

<template><div><canvas ref="canvas" :width="width" :height="height"></canvas></div>
</template><script>
import CanvasWorker from '../workers/canvasWorker.js';export default {name: 'CanvasWorker',data() {return {width: 800,height: 600,worker: null,};},mounted() {const canvas = this.$refs.canvas;if (canvas.transferControlToOffscreen) {const offscreen = canvas.transferControlToOffscreen();this.worker = new CanvasWorker();this.worker.postMessage({ canvas: offscreen, width: this.width, height: this.height },[offscreen] // 必须通过 MessagePort 传递);} else {console.error('OffscreenCanvas 不支持,使用普通 canvas 绘图。');}},beforeDestroy() {if (this.worker) {this.worker.terminate();}},
};
</script><style scoped>
canvas {border: 1px solid #ccc;display: block;margin: 0 auto;
}
</style>

worker.js

self.onmessage = function (e) {const { width, height } = e.data;const offscreenCanvas = e.data.canvas;const ctx = offscreenCanvas.getContext('2d');function draw() {ctx.clearRect(0, 0, width, height);for (let i = 0; i < 500; i++) {ctx.beginPath();ctx.arc(Math.random() * width,Math.random() * height,Math.random() * 20 + 5,0,2 * Math.PI);ctx.fillStyle = `rgba(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255}, 0.8)`;ctx.fill();}requestAnimationFrame(draw);}draw();
};

离屏canvas的优势

1、对于复杂的canvas绘图,可以避免阻塞主线程
2、由于这种解耦,OffscreenCanvas的渲染与DOM完全分离了开来,并且比普通Canvas速度提升了一些

多长时间适合用Web Worker

原则上,运算时间超过50ms会造成页面卡顿,属于Long task,这种情况就可以考虑使用Web Worker,新建一个web worker时, 浏览器会加载对应的worker.js资源,要先考虑worker通信时长的问题,假如一个运算执行时长为100ms, 但是通信时长为300ms, 用了Web Worker可能会更慢
最终标准:
计算的运算时长 - 通信时长 > 50ms,推荐使用Web Worker

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/68415.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

无缝过渡:将 Ansys 子结构模型转换为 Nastran

了解如何将 Ansys 子结构模型无缝转换为 Nastran&#xff0c;以满足有效载荷动态模型要求 Ansys 子结构模型的优势 Ansys 子结构模型为从事大型装配体结构分析和仿真的工程师和分析师提供了多项优势。 这些模型通过将复杂结构划分为更小、更易于管理的子结构&#xff0c;可以…

【Flink系列】4. Flink运行时架构

4. Flink运行时架构 4.1 系统架构 Flink运行时架构——Standalone会话模式为例 1&#xff09;作业管理器&#xff08;JobManager&#xff09; JobManager是一个Flink集群中任务管理和调度的核心&#xff0c;是控制应用执行的主进程。也就是说&#xff0c;每个应用都应该被…

AI刷题-饭馆菜品选择问题、构造回文字符串问题

目录 一、饭馆菜品选择问题 问题描述 测试样例 解题思路&#xff1a; 问题理解 数据结构选择 算法步骤 最终代码&#xff1a; 运行结果&#xff1a; 二、构造回文字符串问题 问题描述 测试样例 解题思路&#xff1a; 解题思路 具体步骤 最终代码&#xff1a;…

使用redis-cli命令实现redis crud操作

项目场景&#xff1a; 线上环境上redis中的key影响数据展示&#xff0c;需要删除。但环境特殊没办法通过 redis客户端工具直连。只能使用redis-cli命令来实现。 操作步骤&#xff1a; 1、确定redis安装的服务器&#xff1b; 2、找到redis的安装目录下 ##找到redis安装目…

讲一下ZooKeeper的持久化机制?

大家好&#xff0c;我是锋哥。今天分享关于【讲一下ZooKeeper的持久化机制&#xff1f;】面试题。希望对大家有帮助&#xff1b; 讲一下ZooKeeper的持久化机制&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 ZooKeeper 是一个开源的分布式协调服务&…

图数据库 | 18、高可用分布式设计(中)

上文我们聊了在设计高性能、高可用图数据库的时候&#xff0c;从单实例、单节点出发&#xff0c;一般有3种架构演进选项&#xff1a;主备高可用&#xff0c;今天我们具体讲讲分布式共识&#xff0c;以及大规模水平分布式。 主备高可用、分布式共识、大规模水平分布式&#xff…

【Python】第二弹---深入理解编程基础:从常量、变量到注释的全面解析

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】【MySQL】【Python】 目录 1、常量和表达式 2、变量和类型 2.1、变量是什么 2.2、变量的语法 2.3、变量的类型 2.4、动态类型特…

生产环境中常用的设计模式

生产环境中常用的设计模式 设计模式目的使用场景示例单例模式保证一个类仅有一个实例&#xff0c;并提供一个访问它的全局访问点- 日志记录器- 配置管理器工厂方法模式定义一个创建对象的接口&#xff0c;让子类决定实例化哪个类- 各种工厂类&#xff08;如视频游戏工厂模式创…

YOLOv10改进,YOLOv10检测头融合RFAConv卷积,添加小目标检测层(四头检测)+CA注意机制,全网首发

摘要 空间注意力已广泛应用于提升卷积神经网络(CNN)的性能,但它存在一定的局限性。作者提出了一个新的视角,认为空间注意力机制本质上解决了卷积核参数共享的问题。然而,空间注意力生成的注意力图信息对于大尺寸卷积核来说是不足够的。因此,提出了一种新型的注意力机制—…

解锁C#语法的无限可能:从基础到进阶的编程之旅

目录 一、C# 基础语法 1.1 数据类型 1.2 变量与常量 1.3 运算符 1.4 控制流语句 二、C# 面向对象编程语法 2.1 类与对象 2.2 封装 2.3 继承 2.4 多态 虚方法 抽象类 接口 三、C# 高级语法 3.1 特性&#xff08;Attribute&#xff09; 预定义特性 自定义特性 3…

“AI智能防控识别系统:守护安全的“智慧卫士”

在如今这个科技飞速发展的时代&#xff0c;安全问题始终是大家关注的焦点。无论是企业园区、学校校园&#xff0c;还是居民社区&#xff0c;都希望能有一双“慧眼”时刻守护着&#xff0c;及时发现并防范各种安全隐患。而AI智能防控识别系统&#xff0c;就像一位不知疲倦、精准…

Leetcode 983. 最低票价 动态规划

原题链接&#xff1a;Leetcode 983. 最低票价 class Solution { public:int mincostTickets(vector<int>& days, vector<int>& costs) {int n days.size();int last days[n - 1];int dp[last 1];map<int, int> mp;for (auto x : days)mp[x] 1;dp…

Vue篇-07

Vue UI组件库 一、移动端常用的UI组件库 1.1、Vant 1.2、Cube UI 1.3、Mint UI 二、PC端常用的UI组件库 2.1、Element UI Element - The worlds most popular Vue UI framework 安装&#xff1a; 按需引入&#xff1a; 135_尚硅谷Vue技术_element-ui按需引入_哔哩哔哩_b…

2025.1.15——四、布尔注入

题目来源&#xff1a;ctfhub技能树 目录 一、基本操作&#xff1a;整理已知信息&#xff0c;得到本题为布尔注入 方法一&#xff1a;手工盲注&#xff08;不推荐&#xff09; step 1&#xff1a;判断具体形式 step 2&#xff1a;查询字段数 step 3&#xff1a;通过回显判…

基于SpringBoot的装修公司管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

小游戏前端地区获取

目前前端获取除了太平洋&#xff0c;没有其它的了。 //在JS中都是使用的UTF-8&#xff0c;然而requst请求后显示GBK却是乱码&#xff0c;对传入的GBK字符串&#xff0c;要用数据流接收&#xff0c;responseType: "arraybuffer" tt.request({url: "https://whoi…

Spark 之 Aggregate

Aggregate 参考链接: https://github.com/PZXWHU/SparkSQL-Kernel-Profiling完整的聚合查询的关键字包括 group by、 cube、 grouping sets 和 rollup 4 种 。 分组语句 group by 后面可以是一个或多个分组表达式( groupingExpressions )。 聚合查询还支持 OLAP 场景下的多…

计算机网络 网络层 2

IP协议&#xff1a; Ip数据报的格式&#xff1a; 首部:分为固定部分 和 可变部分 固定部分是20B 版本&#xff1a;表明了是IPV4还是IPV6 首部长度&#xff1a;单位是 4B&#xff0c;表示的范围是&#xff08;5~15&#xff09;*4B 填充&#xff1a;全0&#xff0c;,让首部变…

JAVA-二叉树的四种遍历

目录 一、二叉树的存储 二、二叉树遍历的概念 1.前序遍历 2.中序遍历 3.后序遍历 4.层序遍历 三、概念面试题 四、代码实现 1.前序遍历 2.中序遍历 3.后序遍历 4.层序遍历 五、其他写法(非递归) 1.非递归前序遍历 2.非递归中序遍历 3.非递归后续遍历 一、二叉树…

Spring FactoryBean到仿照mybatis @Mapper的实现

目录 FactoryBean原理FactoryBean例子org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean mybatis mapper bean的手动实现思考复习下Jdbc传统sql查询做法Mapper接口实现思路复习批量注册beanDefinition: ConfigurationClassPostProcessor自定义实现Mapp…