js采用的是单线程模型,所有任务只能在一个线程上完成,一次只能做一件事。前面的任务没做完,后面的任务只能等着。随着电脑计算能力的增强,尤其是多核 CPU 的出现,单线程带来很大的不便,无法充分发挥计算机的计算能力
相对的webwork就是为js创造多线程的环境,允许主线程创建webwork线程,将未处理的一些任务分给后者 运行.在js主线程运行的同时,work线程在后台运行,两者互不打扰,等到webwork线程的任务结束后,把结果返回给主线程
(1) 同源限制:分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。
(2) DOM限制:Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用document、window、parent这些对象。但是,Worker 线程可以navigator对象和location对象。
(3) 通信联系:Worker 线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息完成
(4) 脚本限制:Worker 线程不能执行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 对象发出 AJAX 请求。
(5) 文件限制: Worker 线程无法读取本地文件,即不能打开本机的文件系统(file://),它所加载的脚本,必须来自网络。
在Vue中使用 web work
1.安装worker-loadernpm 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.完整代码
- <div>
- <button @click="makeWorker">开始线程button>
-
- <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>
计算过程中,页面就不会发生卡顿

开启多线程,并进行计算
回到要解决的问题执行多种运算时,给每种运算开启单独的线程,线程计算完成后要及时关闭
- <div>
- <button @click="makeWorker">开始线程button>
-
- <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.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});
- }
此时数据加载时间会由原来的35s变成6s
1、在 Worker 线程的运行环境中没有 window 全局对象,也无法访问 DOM 对象
2、Worker中只能获取到部分浏览器提供的 API,如定时器、navigator、location、XMLHttpRequest等
3、由于可以获取XMLHttpRequest 对象,可以在 Worker 线程中执行ajax请求
4、每个线程运行在完全独立的环境中,需要通过postMessage、 message事件机制来实现的线程之间的通信
JavaScript 中的多线程 -- Web WorkerOffscreenCanvas-离屏canvas使用说明