先浅浅的了解下浏览器的GUI渲染线程与JS引擎线程
- GUI 渲染线程
① 主要负责页面的渲染,解析 HTML,CSS,构建 DOM 树,布局和绘制等。
② 当页面需要重绘或者由于某种操作引发回流时,将执行该线程。
③ 因为JS可以操作DOM元素,进而会影响GUI的渲染结果,因此JS引擎线程与GUI渲染线程是互斥的。 当执行 JS 引擎线程时,GUI渲染线程会被挂起, 当前任务队列为空时,JS引擎才会去执行 GUI 渲染。 - JS 引擎线程
JS引擎是基于事件驱动,采用单线程运行机制,按照顺序(事件循环机制)从任务列表中取任务,执行任务。
基于此,当js有大量计算时,会造成 UI 阻塞,出现界面渲染卡顿、掉帧等情况,严重时会出现页面卡死的情况,俗称假死
在HTML5的新规范中,实现了 Web Worker 来引入 js 的 “多线程” 技术, 可以让我们可以在页面主运行的 js 线程中加载运行另外单独的一个或者多个 js 线程。
在使用前,检测浏览器是否支持web worker
**if(typeof(Worker)!==“undefined”){
…代码…
}
**
在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”
4、最终代码
<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>
开启多线程,并行计算
回到要解决的问题
执行多种运算时,给每种运算开启单独的线程,线程计算完成后要及时关闭
多线程代码
<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: 'max', name: '最大'},
{type: 'min', 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 sum = (arr) => {
let sum = 0, len = arr.length;
for (let i = 0; i < len; i++) {
sum = numberAdd(Number(arr[i]), sum);
}
return sum;
}
// 数组最大值
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 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 'max':
value = formatNumber(max(arr));
break
case 'min':
value = formatNumber(min(arr));
break
}
// 发送数据事件
postMessage({type, value});
}
基本使用方法
1.创建 web worker 文件:demo_work.js
2.创建 Web Worker 对象
if(typeof(w)=="undefined")
{
w=new Worker("demo_work.js");
w.onmessage=function(event){
........
};
}
Web Worker的限制
1、在 Worker 线程的运行环境中没有 window 全局对象,也无法访问 DOM 对象,parent 对象
2、Worker中只能获取到部分浏览器提供的 API,如定时器、navigator、location、XMLHttpRequest等
3、由于可以获取XMLHttpRequest 对象,可以在 Worker 线程中执行ajax请求
4、每个线程运行在完全独立的环境中,需要通过postMessage、 message事件机制来实现的线程之间的通信
计算时长 超过多长时间 适合用Web Worker
原则:
运算时间超过50ms会造成页面卡顿,属于Long task,这种情况就可以考虑使用Web Worker
但还要先考虑通信时长的问题
通信时长
新建一个web worker时, 浏览器会加载对应的worker.js资源
Time是这个js资源的总时长: 包括加载时间、执行时间
计算的运算时长 - 通信时长 > 50ms,推荐使用Web Worker
什么是postMessage、 message事件机制呢?
html5引入的API,postMessage()方法允许来自不同源的脚本采用异步方式进行有效的通信,可以实现跨文本文档,多窗口,跨域消息传递.多用于窗口间数据通信,这也使它成为跨域通信的一种有效的解决方案;
1.postMessage发送数据之后需要监听message事件,当接收数据就触发这个事件,传过来的对象,记为messageEvent,其中data属性存储了接收的数据,origin和currentTarget属性均存储着发送消息的对象
2.想要在父页面与子页面中传点数据,需要父页面与子页面都允许