Vue2 数万级设备数据全局单例 WebWorker 完整实现

发布时间:2026/9/10 11:13:24

Vue2 数万级设备数据全局单例 WebWorker 完整实现 项目结构src/ ├── worker/ │ └── device.worker.js # Worker后台计算线程筛选/格式化/聚合 ├── utils/ │ └── globalWorker.js # 全局单例Worker管理器主线程封装 └── views/ └── DeviceData.vue # Vue2业务组件调用示例一、全局单例 Worker 管理器 utils/globalWorker.js核心能力单例全局 Worker、taskId 任务标记、过期任务丢弃、循环引用清洗、消息异常捕获、组件销毁终止线程、任务取消队列/** * 全局单例WebWorker管理器 - 设备数据运算专用 * 1. 全局唯一Worker实例所有页面复用避免多线程耗尽 * 2. taskId区分多组件并发任务旧任务返回直接丢弃 * 3. 预处理数据精简字段、清除循环引用适配结构化克隆算法 * 4. 监听messageerror捕获DataCloneError序列化失败 * 5. 简易任务队列支持取消未完成旧运算 * 6. 统一错误捕获不阻塞主线程交互 */ let singletonWorker null; let taskIdSeed 0; // 全局自增任务ID const taskCallbacks new Map(); // 存储taskId对应组件回调 let latestTaskIdMap new Map(); // 每个组件实例最新taskId用于丢弃过期结果 const pendingTaskSet new Set(); // 待执行/执行中任务集合用于取消 // 业务设备仅保留的核心字段剔除冗余嵌套、循环引用对象 const DEVICE_KEEP_FIELDS [ id, deviceName, statusCode, online, areaId, areaName, deviceType, createTime, voltage, temperature, power ]; /** * 清除对象循环引用 精简业务字段解决postMessage DataCloneError * param {ArrayObject} rawList 原始设备数组含循环引用/冗余属性 * returns {ArrayObject} 可安全跨线程传输的纯数据 */ function cleanDeviceData(rawList) { const visited new WeakMap(); // 标记已遍历对象切断循环引用 return rawList.map(item { // 递归清洗单个对象剔除循环引用与无用字段 const cleanObj {}; DEVICE_KEEP_FIELDS.forEach(key { const val item[key]; // 过滤DOM、函数、循环嵌套对象 if (typeof val function || val null || val undefined) return; if (typeof val object val ! Date) { if (visited.has(val)) return; // 遇到循环引用直接丢弃该嵌套对象 visited.set(val, true); } cleanObj[key] val; }); return cleanObj; }); } class GlobalDeviceWorker { constructor() { // 单例拦截 if (singletonWorker) return singletonWorker; singletonWorker this; this.worker null; this.initWorker(); } /** 初始化全局Worker实例绑定所有监听事件 */ initWorker() { // webpack worker-loader引入worker脚本 const WorkerConstructor require(../worker/device.worker.js); this.worker new WorkerConstructor(); // 1. 监听Worker正常消息返回 this.worker.addEventListener(message, (e) { const { taskId, result, success } e.data; // 无taskId直接忽略 if (!taskId) return; // 核心判断是否为当前组件最新任务旧任务结果直接丢弃不更新UI const compLatestId latestTaskIdMap.get(taskCallbacks.get(taskId)?.compUid); if (compLatestId ! taskId) { pendingTaskSet.delete(taskId); return console.log(丢弃过期任务${taskId}已有新计算发起); } // 执行组件回调 const taskInfo taskCallbacks.get(taskId); if (!taskInfo) return; const { resolve, reject } taskInfo; pendingTaskSet.delete(taskId); taskCallbacks.delete(taskId); success ? resolve(result) : reject(result); }); // 2. 捕获结构化克隆失败循环引用、非法数据类型触发messageerror this.worker.addEventListener(messageerror, (err) { console.error(Worker数据序列化失败循环引用/非法字段, err); // 清空所有loading通知组件报错 taskCallbacks.forEach((info, tid) info.reject(数据解析失败请精简设备数据)); taskCallbacks.clear(); pendingTaskSet.clear(); }); // 3. 捕获Worker内部代码运行异常 this.worker.addEventListener(error, (err) { console.error(Worker线程计算异常, err.message, err.lineno); taskCallbacks.forEach((info, tid) info.reject(数据统计失败请重试)); taskCallbacks.clear(); pendingTaskSet.clear(); }); } /** * 发起设备数据批量运算筛选格式化聚合 * param {String} compUid 组件唯一标识区分不同页面组件 * param {Array} rawDeviceList 原始数万级设备数组 * param {Object} filterParams 多条件筛选参数 {status, online, areaList, typeList} * returns {PromiseObject} {filterList:格式化后数组, stat:聚合统计结果} */ runDeviceCalc(compUid, rawDeviceList, filterParams) { return new Promise((resolve, reject) { // 1. 生成全局唯一taskId const taskId taskIdSeed; // 2. 更新组件最新taskId旧任务标记过期 latestTaskIdMap.set(compUid, taskId); // 3. 预处理清洗循环引用、精简传输字段减小传输体积 const cleanData cleanDeviceData(rawDeviceList); // 4. 缓存当前任务回调供Worker返回时分发 taskCallbacks.set(taskId, { compUid, resolve, reject }); pendingTaskSet.add(taskId); // 5. 发送消息至Worker携带taskId区分任务 try { this.worker.postMessage({ taskId, type: DEVICE_BATCH_CALC, payload: { deviceList: cleanData, filterParams } }); } catch (postErr) { // 发送消息前捕获序列化报错 reject(数据传输失败存在循环引用或非法对象); pendingTaskSet.delete(taskId); taskCallbacks.delete(taskId); } }); } /** 取消当前组件所有未完成运算切换筛选条件时丢弃旧计算 */ cancelComponentTask(compUid) { latestTaskIdMap.delete(compUid); // 清除该组件下所有待执行任务回调 const delTaskIds []; taskCallbacks.forEach((info, tid) { if (info.compUid compUid) delTaskIds.push(tid); }); delTaskIds.forEach(tid { taskCallbacks.delete(tid); pendingTaskSet.delete(tid); }); } /** 组件卸载时终止全局Worker仅页面完全销毁调用全局共用谨慎调用 */ terminateWorker() { if (this.worker) { this.worker.terminate(); this.worker null; singletonWorker null; taskCallbacks.clear(); pendingTaskSet.clear(); latestTaskIdMap.clear(); } } } // 导出全局单例实例 export default new GlobalDeviceWorker();二、Worker 后台计算脚本 worker/device.worker.js独立线程执行三类耗时逻辑批量多条件筛选、数据格式化、聚合统计无 DOM 操作/** * WebWorker后台设备数据运算线程 * 三大耗时逻辑全部移入此处不阻塞主线程UI渲染 * 1. 批量多条件组合过滤设备 * 2. 统一数据清洗格式化时间/状态码/单位/空值 * 3. 聚合统计总数、在线离线分组、设备类型求和、均值计算 * 通信规范接收携带taskId返回结果带回taskId用于主线程分发 */ self.onmessage function (e) { const { taskId, type, payload } e.data; try { if (type DEVICE_BATCH_CALC) { const { deviceList, filterParams } payload; // 步骤1多条件批量筛选 const filteredList batchFilterDevice(deviceList, filterParams); // 步骤2数据格式化、空值清洗、单位转换、状态转中文 const formatList formatDeviceData(filteredList); // 步骤3聚合统计计算 const statResult aggregateDeviceStat(formatList); // 返回结果携带taskId主线程匹配分发 self.postMessage({ taskId, success: true, result: { filterList: formatList, stat: statResult } }); } } catch (calcErr) { // Worker内部计算异常回传错误信息 self.postMessage({ taskId, success: false, result: calcErr.message || 设备数据运算失败 }); } }; // 1. 批量多条件组合筛选 /** * 多字段联合过滤状态、在线状态、区域、设备类型多条件匹配 * param {Array} list 清洗后的设备数组 * param {Object} params 筛选条件 * returns {Array} 筛选后原始数据数组 */ function batchFilterDevice(list, params) { const { status, online, areaList [], typeList [] } params; return list.filter(item { // 状态筛选不传则不限制 if (status ! undefined item.statusCode ! status) return false; // 在线离线筛选 if (online ! undefined item.online ! online) return false; // 区域多选匹配 if (areaList.length !areaList.includes(item.areaId)) return false; // 设备类型多选匹配 if (typeList.length !typeList.includes(item.deviceType)) return false; return true; }); } // 2. 数据格式化、空值清洗、单位转换 /** * 统一格式化逻辑时间格式化、状态码转中文、数值换算、空值清洗 * param {Array} rawFilterList 筛选后数组 * returns {Array} 格式化完成业务数组 */ function formatDeviceData(rawFilterList) { // 状态码映射中文标签 const statusMap { 0: 正常, 1: 故障, 2: 维护, 3: 停用 }; return rawFilterList.map(item { const target { ...item }; // 1. 空值清洗空字符串/undefined统一转为- Object.keys(target).forEach(key { if (target[key] || target[key] null || target[key] undefined) { target[key] -; } }); // 2. 状态码转中文 target.statusText statusMap[target.statusCode] || 未知状态; // 3. 时间格式化时间戳转YYYY-MM-DD HH:mm if (target.createTime ! -) { const date new Date(target.createTime); const y date.getFullYear(); const m String(date.getMonth() 1).padStart(2, 0); const d String(date.getDate()).padStart(2, 0); const h String(date.getHours()).padStart(2, 0); const min String(date.getMinutes()).padStart(2, 0); target.createTimeText ${y}-${m}-${d} ${h}:${min}; } // 4. 数值单位换算电压mV转V、功率w转kw if (target.voltage ! -) target.voltageV (target.voltage / 1000).toFixed(2); if (target.power ! -) target.powerKw (target.power / 1000).toFixed(2); return target; }); } // 3. 聚合统计计算 /** * 聚合指标总数、在线离线分组、各类型数量、温度/电压平均值 * param {Array} formatList 格式化完成设备数组 * returns {Object} 统计指标 */ function aggregateDeviceStat(formatList) { const total formatList.length; let onlineCount 0; let offlineCount 0; const typeGroup {}; // 设备类型分组计数 let tempSum 0; let voltageSum 0; let validTempNum 0; let validVoltNum 0; formatList.forEach(item { // 在线离线统计 if (item.online true) onlineCount; else offlineCount; // 设备类型分组求和 const type item.deviceType; typeGroup[type] (typeGroup[type] || 0) 1; // 温度均值累加 if (!isNaN(Number(item.temperature)) item.temperature ! -) { tempSum Number(item.temperature); validTempNum; } // 电压均值累加 if (!isNaN(Number(item.voltageV)) item.voltageV ! -) { voltageSum Number(item.voltageV); validVoltNum; } }); return { totalDevice: total, onlineCount, offlineCount, typeStat: typeGroup, avgTemperature: validTempNum ? (tempSum / validTempNum).toFixed(1) : -, avgVoltage: validVoltNum ? (voltageSum / validVoltNum).toFixed(2) : - }; }三、Vue2 业务组件调用示例 views/DeviceData.vue包含筛选切换取消旧任务、loading 控制、异常提示、组件卸载终止 Worker、唯一组件 ID 区分多页面template div classdevice-page div classfilter-bar select v-modelfilterParams.status changehandleSearch option :valueundefined全部状态/option option :value0正常/option option :value1故障/option /select select v-modelfilterParams.online changehandleSearch option :valueundefined全部在线/option option :valuetrue在线/option option :valuefalse离线/option /select button clickhandleSearch :disabledloading查询设备数据/button /div div v-ifloading数据运算中请稍候.../div div v-iferrMsg stylecolor:red{{ errMsg }}/div !-- 聚合统计展示 -- div v-ifstatData p设备总数{{ statData.totalDevice }}/p p在线{{ statData.onlineCount }}台 / 离线{{ statData.offlineCount }}台/p p平均温度{{ statData.avgTemperature }}℃/p /div !-- 格式化后设备表格 -- table v-iftableList.length tr v-foritem in tableList :keyitem.id td{{ item.deviceName }}/td td{{ item.statusText }}/td td{{ item.createTimeText }}/td td{{ item.voltageV }}V/td /tr /table /div /template script import globalWorker from /utils/globalWorker; // 模拟数万级原始设备数据接口返回含冗余字段/循环引用 function mockRawDeviceData(count 15000) { const list []; for (let i 1; i count; i) { const item { id: i, deviceName: 设备_${i}, statusCode: Math.floor(Math.random() * 4), online: Math.random() 0.4, areaId: area_${Math.floor(Math.random() * 5)}, areaName: 区域${Math.floor(Math.random() * 5)}, deviceType: [A1, B2, C3][Math.floor(Math.random() * 3)], createTime: Date.now() - Math.floor(Math.random() * 86400000 * 30), voltage: Math.floor(Math.random() * 10000), temperature: Math.floor(Math.random() * 60), power: Math.floor(Math.random() * 5000), // 冗余嵌套对象存在循环引用测试清洗逻辑 extraInfo: {}, }; item.extraInfo.parent item; // 制造循环引用 list.push(item); } return list; } export default { name: DeviceData, data() { return { compUid: device_list_page_01, // 当前组件唯一标识 loading: false, errMsg: , filterParams: { status: undefined, online: undefined, areaList: [], typeList: [] }, rawAllDevice: mockRawDeviceData(15000), // 15000条模拟设备 tableList: [], statData: null }; }, methods: { /** 触发筛选运算切换条件自动取消上一次未完成任务 */ async handleSearch() { this.loading true; this.errMsg ; // 核心切换筛选条件时取消当前组件未完成旧运算丢弃过期计算 globalWorker.cancelComponentTask(this.compUid); try { // 调用全局Worker执行三大耗时运算 const { filterList, stat } await globalWorker.runDeviceCalc( this.compUid, this.rawAllDevice, this.filterParams ); this.tableList filterList; this.statData stat; } catch (err) { // 捕获序列化异常、Worker计算异常友好提示 this.errMsg err || 数据统计失败请重新筛选; console.error(设备计算任务失败, err); } finally { this.loading false; } } }, beforeDestroy() { // 组件卸载取消未完成任务全局Worker共用不直接terminate globalWorker.cancelComponentTask(this.compUid); // 如需完全关闭全局线程整个应用退出才调用globalWorker.terminateWorker() } }; /script四、Webpack (vue-cli) 配置支持 worker-loadervue-cli 项目安装依赖npm install worker-loader -Dvue.config.js 添加 loader 匹配 worker 文件module.exports { configureWebpack: { module: { rules: [ { test: /\.worker\.js$/, use: { loader: worker-loader } } ] } } }关键需求点全覆盖说明全局单例 Worker采用单例模式全局仅创建 1 个后台线程所有页面组件共用避免浏览器创建过多线程耗尽资源。taskId 任务隔离、丢弃过期结果每个运算生成自增唯一 taskId绑定组件 compUid 区分多页面并发发起新筛选时更新组件最新 taskId旧任务返回直接丢弃不更新 UI切换筛选条件主动取消未完成旧计算减少无效后台运算结构化克隆兼容处理循环引用报错解决cleanDeviceData预处理WeakMap 标记循环对象、剔除嵌套循环引用仅保留业务核心字段过滤冗余属性 / 函数 / DOM大幅减小传输数据体积捕获messageerror监听 DataCloneError 序列化失败给用户提示三大耗时逻辑全部移入 Worker批量多条件过滤状态、在线、区域、设备类型多字段联合筛选数据格式化空值清洗、状态码转中文、时间格式化、电压 / 功率单位换算聚合统计设备总数、在线离线分组、设备类型分组计数、温度电压平均值完整异常捕获体系worker.onerror捕获 Worker 内部代码计算报错messageerror捕获跨线程数据序列化失败循环引用 / 非法对象postMessage 外层 try-catch捕获发送消息阶段序列化异常统一清空 loading返回友好文案不阻塞页面滚动、点击交互组件生命周期资源释放beforeDestroy钩子取消当前组件所有未完成任务提供terminateWorker方法应用完全退出时彻底销毁线程释放内存数据传输特性postMessage 基于结构化克隆算法传输数据为独立副本完全非引用传递Worker 内部修改数据不会污染主线程原始数组。
延伸阅读

更多相关文章

2026/9/6 18:15:27

改稿速度拉满 2026 最新降AI率工具测评与推荐

2026年真正好用的AI论文降重与改写工具,核心看降重效果、去AI味、格式保留、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 …

2026/9/10 11:18:29

HCIA-华为数通基础理论与实践10

本文主要聚焦于双机热备实验,重点揭露了模拟器(PT)与现实设备的差异,特别是5500V与6000V版本的兼容性问题。 目录前言一、 双机热备的底层逻辑与分类1.为什么需要双机热备?2.两种热备形态的本质区别二、 路由器热备&am…

2026/9/10 12:47:42

四步做出轻量版 Windows 11 镜像:Tiny11Builder 完整指南

四步做出轻量版 Windows 11 镜像:Tiny11Builder 完整指南 【免费下载链接】tiny11builder Scripts to build a trimmed-down Windows 11 image. 项目地址: https://gitcode.com/GitHub_Trending/ti/tiny11builder 官方 Windows 11 系统里塞着不少预装应用&am…

2026/9/10 12:42:40

CANN/GE算子编译注册函数

aclopRegisterCompileFunc 【免费下载链接】ge GE(Graph Engine)是面向昇腾的图编译器和执行器,提供了计算图优化、多流并行、内存复用和模型下沉等技术手段,加速模型执行效率,减少模型内存占用。 GE 提供对 PyTorch、…

2026/9/9 13:11:35

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/10 11:16:38

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/9 16:31:09

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/10 0:00:55

目录对比去重实战:用哈希算法精准清理重复文件

我电脑里现在还有一块换了三次机的“数据墓地”硬盘,里面存着2016年以前所有旧笔记本的完整备份。平时不觉得有什么,直到前阵子想把它整理归档,发现同一个安装包、同一批照片、同一份论文草稿,在几个不同的备份目录里反复出现。更…

2026/9/10 0:00:55

Leaflet离线地图完整Demo合集:内网部署与坐标纠偏实战

简介:这是一份面向Web GIS开发者的LeafLet离线地图示例合集,帮助开发者快速掌握离线地图从搭建到交互的完整流程。压缩包共723个文件,大小14.06MB,以319个js脚本、175个html页面和29个css样式文件为主体,配合png/svg图…

2026/9/10 0:00:55

MATLAB读取Rinex 3.02观测文件:多系统GNSS数据解析实战

简介:基于MATLAB开发的Rinex3.02版观测文件(o文件)读取代码包,面向卫星定位导航方向的学习者与研究人员,用于解决新版观测文件的数据解析、历元提取与时间转换问题。压缩包共4个文件,包含两个m脚本、一个19…

2026/9/10 12:32:02

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/7 22:46:00

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/9 10:21:54

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码