背景
js是单线程这是大家都知道,为了防止多个线程同时操作DOM,这个导致一个复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准。
webWorker
web worker是 HTML5 标准的一部分,这一规范定义了一套 API,允许我们在 js 主线程之外开辟新的 Worker 线程,并将一段 js 脚本运行其中,它赋予了开发者利用 js 操作多线程的能力。
因为是独立的线程,Worker 线程与 js 主线程能够同时运行,互不阻塞。所以,在我们有大量运算任务时,可以把运算任务交给 Worker 线程去处理,当 Worker 线程计算完成,再把结果返回给 js 主线程。这样,js 主线程只用专注处理业务逻辑,不用耗费过多时间去处理大量复杂计算,从而减少了阻塞时间,也提高了运行效率,页面流畅度和用户体验自然而然也提高了。
常见问题
场景一:人脸识别和活体检测
在这两个场景中会通过ws实时上传图片(加密)
这里面主要是两个问题:
(1)图片加密频繁操作,导致占用主线程,页面出现卡顿卡死情况
(2)定时器延时,上面操作导致定时器并不是开始规定的5秒可能是7秒8秒
场景二:大规模数据上传
比如有个项目前端拿到很大量据需要,需要后处理后再上传服务器。
处理数据可能会占用主线程导致页面显示不流程,卡顿等情况
场景三:请求过于频繁,读写操作过多
同上
场景四:视频音频转码、转格式、加密等操作。
和场景一一样,大量耗费计算资源。阻塞主线程,导致页面流畅度下降,响应降低。
解决以上问题可以考虑使用webwork
怎么使用?
要使用先了解它
Web Worker的限制
1、在 Worker 线程的运行环境中没有 window 全局对象,也无法访问 DOM 对象。
2、Worker中只能获取到部分浏览器提供的 API,如定时器、navigator、location、XMLHttpRequest等。
3、由于可以获取XMLHttpRequest 对象,可以在 Worker 线程中执行ajax请求。
4、每个线程运行在完全独立的环境中,需要通过postMessage、 message事件机制来实现的线程之间的通信。
worker.postMessage:向 worker 的内部作用域发送一个消息,消息可由任何 JavaScript 对象组成
worker.terminate:
立即终止 worker。该方法并不会等待 worker 去完成它剩余的操作;worker 将会被立刻停止
worker.onmessage:
当 worker 的父级接收到来自其 worker 的消息时,会在 Worker 对象上触发message 事件
worker.onerror:
当 worker 出现运行中错误时,它的 onerror事件处理函数会被调用。它会收到一个扩展了
ErrorEvent 接口的名为 error 的事件
我直接上代码,我常用(不,是只会)vue,看在项目是怎么使用的。
我写了几个worker.js
1:msg.worker.js 为了消息推送
onmessage = function(e) { console.log(e) postMessage(e.data.num); // close(); } import MsgWorker from "./demo/msg.worker"; // mounted 初始化 this.msgWorker = new MsgWorker (); this.worker.onmessage = (event) => { console.log(event.data); this.result = event.data; console.log("主线程收到回复,即将关闭worker连接", this.index); // this.worker.terminate(); }; methods: { useWorker() { this.msgWorker.postMessage({ text: "当前时间:", num: Date.now() }); }, },
想想:他能干嘛?
利用策略模式,全局监听操作,和vue Bus 功能一样,使用在页面交互功能一样。
有人说和写个全局方法不一样吗?
不一样,它不占用js的主线程
2:time.worker.js添加定时器
onmessage = function (e) { setTimeout(() => { postMessage(Date.now()); }, e.data.time); // close(); }; import TimeWorker from "./demo/time.worker"; //初始化mounted mounted() { this.timeWorker = new TimeWorker(); this.timeWorker.onmessage = (event) => { this.result = event.data; console.log("定时完成"); }; }, //方法调用 methods: { useWorker() { this.timeWorker.postMessage({ time: 10000 }); }, },
定时器能干嘛?如果进来先执行 上面定时器useWorker() 再执行下面计算demo()
它能定时输出时间,不被js主线程阻塞影响。
demo() { console.log("Start", Date.now()); let i = 0; for (let index = 0; index < 100000000000; index++) { i += index; } console.log(i, Date.now()) console.log("End", Date.now()); }
3:canvas.worker.js canvas绘制
<template> <div class="canvas-demo"> <button @click="makeWorker">开始绘图</button> <canvas id="myCanvas" width="300" height="150"></canvas> </div> </template> <script> import Worker from "./demo/canvas.worker"; export default { methods: { makeWorker() { let worker = new Worker(); let htmlCanvas = document.getElementById('myCanvas'); // 使用canvas的transferControlToOffscreen函数获取一个OffscreenCanvas对象 let offscreen = htmlCanvas.transferControlToOffscreen(); // 注意:第二个参数不能省略 worker.postMessage({ canvas: offscreen }, [offscreen]); } } }; </script> <style lang="less"> .canvas-demo { padding: 20px; } </style>
canvas.worker.js
onmessage = function (e) { // 使用OffscreenCanvas(离屏Canvas) let canvas = e.data.canvas; // 获取绘图上下文 let ctx = canvas.getContext('2d'); // 绘制一个圆弧 ctx.beginPath(); // 开启路径 ctx.arc(150, 75, 50, 0, Math.PI * 2); ctx.fillStyle = '#333333'; //设置填充颜色 ctx.fill(); //开始填充 ctx.stroke(); };
4:xhrWorker.js接口调用
<template> <div> <button @click="useWorker">开始线程</button> </div> </template> <script> import { fetchApi} from "./demo/xhrWorker.js"; export default { data() { return { worker: null, }; }, mounted() { const blob = fetchApi(); this.worker = new Worker(blob); // 使用上面import进来的js,名字为 demo.worker.worker.js,不可配置,路径相对比较灵活,需要worker-loader this.worker.onmessage = (event) => { console.log(event.data); console.log("主线程收到回复,即将关闭worker连接", event.data); // this.worker.terminate(); }; this.worker.onerror = (event) => { console.log(event.data); }; }, methods: { useWorker() { this.worker.postMessage({ url: `http://xx.xx.xx.xx:8888/login`, data: { password: "admin", username: "admin123456", }, responseType: "json", method: "POST", id: Date.now(), }); }, }, // 页面关闭,如果还没有计算完成,要销毁对应线程 beforeDestroy() {}, }; </script>
xhrWorker.js 我尝试将他写成 xhr.worker.js 结果获取不到fetchApi方法,可以注意下
export function fetchApi() { const workerCode = ` self.addEventListener('message', async function (e) { const { url, data, responseType, method, id } = e.data const xhr = new XMLHttpRequest() xhr.open(method, url, true) xhr.responseType = responseType xhr.setRequestHeader('Content-Type', 'application/json') xhr.onload = function () { if (xhr.status === 200) { self.postMessage({ xhrRes: xhr.response, id }) } else { self.postMessage({ error: 'error' }) } } xhr.onerror = function () { self.postMessage({ error: 'error' }) } xhr.send(JSON.stringify(data)) }) ` const blob = new Blob([workerCode], { type: 'application/javascript' }) const blobUrl = URL.createObjectURL(blob) return blobUrl }
5:dataWorker.js数据计算
<template> <div> <div class="data-lsit"> <div class="=data-item" v> <span>数据</span> </div> </div> <button @click="makeWorker">开始线程</button> <!--在计算时 往input输入值时 没有发生卡顿--> <p><input type="text" /></p> </div> </template> <script> import Worker from "./demo/math.worker"; export default { data() { // 模拟数据 let arr = new Array(20).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> 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.data let value = ''; switch (type) { case 'sum': value = formatNumber(sum(arr)); break case 'average': value = formatNumber(average(arr)); break case 'weightedAverage': value = formatNumber(weightedAverage(arr, weightedList)); break case 'max': value = formatNumber(max(arr)); break case 'middleNum': value = formatNumber(middleNum(arr)); break case 'min': value = formatNumber(min(arr)); break case 'variance': value = formatNumber(variance(arr)); break case 'popVariance': value = formatNumber(popVariance(arr)); break case 'stdDeviation': value = formatNumber(stdDeviation(arr)); break case 'popStandardDeviation': value = formatNumber(popStandardDeviation(arr)); break } // 发送数据事件 postMessage({type, value}); }
总结
以上列子分了五个场景,直接引入项目就可以测试,最后一个例子请参考:一文彻底了解Web Worker,十万条数据都是弟弟附带源码。
写这篇文章为了巩固下webWorker的使用,希望能对你们有所帮助,如果有帮助请点个赞。谢了