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

发布时间:2026/11/4 8:58:49

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/11/4 8:58:49

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

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

2026/11/2 10:08:52

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

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

2026/11/4 8:54:17

PostgreSQL Schema管理与优化实战指南

1. PostgreSQL Schema基础概念解析 在PostgreSQL数据库体系中,schema(模式)是一个至关重要的逻辑容器概念。简单来说,它就像是数据库里的文件夹系统,允许我们将表、视图、函数等数据库对象进行分组管理。每个PostgreSQ…

2026/11/4 8:54:17

Agent静默截断排查:源端50条数据为何模型只看到20条

做 Agent 的同学应该都有过这种经历:你明明在源端准备好了 50 条候选数据,模型回答里却只体现出了 20 条的信息。它不报错、不警告、不告诉你少了一半,就静悄悄地用剩下那部分内容给你交差。我第一次遇到这个问题是在调一个检索增强型 Agent …

2026/11/4 8:54:17

LEF/DEF 5.8 格式规范详解:物理设计中避坑与DRC通关的核心依据

简介:本资源是Cadence官方发布的《LEF/DEF 5.8 语言参考》PDF手册,面向集成电路物理设计工程师、EDA工具使用者及VLSI课程学习者,系统解决LEF/DEF格式在工艺建模、单元库定义、布局布线描述与跨工具数据交换中的核心语法与规范理解问题。文件…

2026/11/4 8:54:17

PostgreSQL Schema管理与最佳实践指南

1. PostgreSQL Schema基础概念解析 在PostgreSQL数据库体系中,schema是一个非常重要的逻辑容器概念。简单来说,schema就像是数据库里的文件夹,它可以帮助我们更好地组织和管理数据库对象。每个schema可以包含表、视图、索引、序列、数据类型、…

2026/11/4 8:54:17

用Rerank做文本分类?16组实验全记录与踩坑复盘

1. 先说说我为什么动了这个念头前两天接了个需求:一批业务场景文本,要判定它们是否属于某个风险类型。数据量不死多,几千条,但要求精度高,而且语言混杂,不是那种标准化的模板文本。最要紧的是数据不能出内网…

2026/11/4 8:49:16

Montserrat字体选型与Web落地:字重、可变字体与性能优化

1. Montserrat 到底是什么,凭什么被用烂了如果你最近在接手品牌官网、SaaS 后台或者电商详情页,翻开源码或者设计稿,十有八九会撞见 Montserrat 这个名字。它几乎成了“现代几何无衬线”这个类别的默认答案:免费商用、九档字重、还…

2026/11/3 7:47:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/3 14:24:53

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/4 0:03:20

AI辅助技术栈评估:提升研发决策效率60%

1. 项目背景与核心价值技术决策一直是研发团队面临的重大挑战。随着技术生态的快速演进,技术栈的选择、维护和迭代变得愈发复杂。我们团队在过去两年里,先后经历了三次重大技术栈迁移,每次都需要投入大量人力进行技术评估和风险分析。直到去年…

2026/11/4 0:03:20

从生成Token到直接决策:Jev如何重塑AI Agent工程实践

1. 从"生成文字"到"拍板决策":Jev 到底在解决什么问题大多数人第一次接触大模型,脑子里建立的模型是"输入问题、输出文字"。你问它今天天气怎么样,它给你一段描述;你让它写个周报,它给你…

2026/11/4 0:03:20

32G统一内存跑大模型:内存账、TPS实测与端云分流

想在一台 32G 统一内存的小主机上把大模型跑成"日常可用",这件事我前后折腾了小半年。标题里说的这台 Mac mini M6,是我们这次讨论的目标平台,具体到手规格以实物为准;但下面这套推算方法和实测思路,对任何统…

2026/11/3 7:47:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/3 14:24:53

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

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

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

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