什么是 Web Worker
15 年前,也就是 2008 年,html
第五版 html5
公布,这一版的公布,提供了不少新的好用的性能,如:
- Canvas 绘图
- 拖拽 drag
- websocket
- Geolocation
- webworker
- 等 …
笔者之前说过:一项新技术新的技术计划的提出,肯定是为了解决某个问题的,或者是对某种计划的优化
那么 Web Worker
这个新技术解决了什么问题?有哪些优化价值呢?
让咱们持续往下看 …
官网定义
Web Worker 为 Web 内容在后盾线程中运行脚本提供了一种简略的办法。线程 能够执行工作而不烦扰用户界面。此外,他们能够应用 XMLHttpRequest 执行 I/O (只管 responseXML 和 channel 属性总是为空)。一旦创立,一个 worker 能够将音讯发送到创立它的 JavaScript 代码,通过将音讯公布到该代码指定的事件处理程序(反之亦然)……
官网 MDN 地址:https://developer.mozilla.org…
乍一看这段话,如同懂了(Web Worker 和线程
无关),又如同没懂。然而咱们能看到一个加粗的关键字:线程。
那么,新的问题来了,什么是线程?
当咱们去学习一个知识点
A
的时候,咱们会发现知识点 A 是由a1、a2、a3、a4
组合而成的,所以咱们要持续下钻,去搞懂a1、a2、a3、a4
别离是什么意思。这样能力更好的了解知识点A
的含意内容。
什么是线程
线程
根本八股文定义不赘述,大家能够将 线程
了解成为一个打工仔,每天的工作就是 解析翻译并执行代码
,像搬砖一样,不过一次只能搬一块砖,前面有砖,不好意思,你等会儿,等我把这个砖搬完了再搬你们。
线程道人朗声道:尔等砖头列队稍后,待老夫一块块搬!(js 中的工作队列
)
js
是单线程语言
,生来便是!java
平时写代码也是单线程的操作
,不过单线程
有时候不太够用,于是java
中也提供了多线程
的操作入口,比方:Thread 类
,Runnable 接口
,Callable 接口
;大多数语言都是相似的,python
也是的啊,python
写代码平时也是复线
程,另也提供了threading 模块
让程序员能够去做多线程操作
。
为何 js 要设计成单线程呢?
- 合乎大抵趋势
- 这与
js
的工作内容无关:js
只是用来去做一些用户交互,并出现成果内容。 - 试想,如果 js 是多线程,线程一将
dom 元素
的背景色改成红色,线程二将dom 元素
的背景色改为绿色,那么,到底上红色还是绿色呢? - 不过起初人们发现,某些状况下,
前端如果能做多线程的操作
,将会大大晋升开发效率 - 于是
Web Worker
就应运而生了 Web Worker
能够创立另外的线程去做一些操作,操作不影响js
主线程(比方UI 渲染
)- 原本只有一个
js 主线程
搬砖,当初又过去好几个js 辅助线程
一块帮忙搬砖,那必定效率高啊!
有点道友问,那如果主线程操作 dom,而后在 Web worker 创立的辅助线程中也去操作 dom 呢?最终后果听谁的啊???答复:Web work 创立的线程中没有 document 全局文档对象,所以无奈操作 dom,另外,也不会这样做的
- 大家这样了解:在理论开发场景中
Web Worker
次要,少数,利用在前端一些简单中运算 - 而大家都晓得一些简单的运算,基本上交由后端去解决(实际上简单的运算操作,后端也会看状况去开启多线程操作的)
- 这样说来,
Web Worker
的作用就是把后端进行的多线程操作运算拿到前端了
- 工作中一些数据的加工、计算、组装逻辑操作,经常是由后端来干;然而在某些状况下,如果前端去做的话,效率或者更高一些
所以 Web Worker
这个新技术的价值是什么呢?
Web worker 创立辅助线程、帮忙前端主线程进行简单耗时的计算
一个人手不够用,那就多摇几个人。
Web worker 创立的一些辅助线程,别离去帮主线程分担一些简单的、耗时的 js 运算,这样的话,主线程后续的代码执行就不会阻塞,当辅助线程计算出简单耗时运算后果后,再与主线程通信,将计算出的后果告知主线程。
Web Worker
新技术价值,简而言之:晋升前端代码运算执行效率
对于 Web worker
的原生语法,诸如:
// 创立一个 Web worker
var myWorker = new Worker('worker.js');
// 应用 Web worker 发送音讯
worker.postMessage(params)
// 应用 Web worker 接管音讯
worker.onmessage = function(e) {console.log(e.data)
}
// 等等...
Web worker
的原生语法,笔者不赘述了,大家可自行去 MDN
上的 Web Worker 去查看,
为什么不说呢?因为咱们工作中开发代码,基本上都是应用框架,在框架中间接写原生的 Web worker
有许多的边界异样或者其余的状况须要管制。以 vue
框架为例,咱们不能间接写 Web Worker
,须要应用Webpack
中的 worker-loader
插件去解析 Web worker
,并且在vue.config.js
中去做相应配置。
嗯,有些麻烦。
在这个状况下,基于 Web Worker
进行封装的插件库vue-worker
,就闪亮退场了。
简略、好用、便于了解
这里不提 Web worker 的原生根本语法不是说大家不必看了,看还是要看的,只不过篇幅起因(懒),这里就不赘述了。
Web Worker 的利用场景
如果大家只是写增删改查的业务代码,Web Worker 用的确实非常少
工作中那些须要进行简单耗时的计算的逻辑代码,都能够由前端应用
Web Worker
进行计算。然而简单耗时的计算基本上都是交由后端进行。这个广泛认知下导致其应用的比拟少。
大略有以下场景:
- 加密解密数据(加密解密笔者之前接触的是
NodeRSA、md5、crypto
) - 提前获取数据,比方提前发送
ajax
申请获取接口的一些图片、数值等相干数据 - 将耗时运算交由
Web Work
解决(笔者之前做的就是这个)
不过应用的是
vue-worker
插件(基于Web Worker
封装的插件)
vue-worker(基于 Web worker 封装的一款不错的插件)
当咱们找到一款还不错的插件的时候,咱们首先去 npm
官网上看看这个插件的下载量如何,以及 GitHub
的star
数量多少。
npm
地址:https://www.npmjs.com/package…
vue-worker
下载量截图:
好少啊,一共也才不到 3000 次
下载量,这里的下载量少是因为,利用场景不多,所以大家应用的也是少,接下来谈谈本人已经应用 Web worker
的这个场景
已经利用场景
笔者之前做一个我的项目,页面上有很多的输入框,外面的输入框中须要填写硬件采集的:压力、温度、比热容、品质大小等很多参数,当点击计算按钮时,会把这些参数进行计算,得出的后果,以供工作人员进行工作参考。
相当于一个 计算器
,只不过 计算工夫会很长
原本说是把这个计算过程交给后端计算,只不过过后后端的共事有事件,销假一周。然而工作不能停下啊
于是笔者就想能不能前端计算呢?
通过一系列调研(谷歌一下),找到了 vue-worker
插件
最终是由笔者这个前端去做的这个计算,工夫挺好,产品泣不成声
应用步骤
1. 下载依赖包
cnpm i vue-worker --save
2. 在入口文件中引入
import Vue from 'vue'
import VueWorker from 'vue-worker' // Web worker 插件
Vue.use(VueWorker)
3. 应用
接下来,笔者举两个例子,更加便于大家了解
案例一 主线程渲染 dom、辅助线程进行计算
需要:
- 点击按钮进行计算,计算执行两个
UI 操作
- 第一个
UI 操作
,计算斐波那契数fib(44)
的值,且统计计算所用的时长,并写入到页面上 - 第二个
UI 操作
,每隔0.1 秒
,更新页面上h2 标签
的内容值 - 要求两个操作
不阻塞
,不能呈现后一个UI 操作
要期待前一个UI 操作
的状况 - 因为
斐波那契是递归执行
,是一个比拟耗时的操作fib(44)
- 那就想方法不让这个耗时的操作梗塞住工作队列
咱们应用 vue-worker
提供的办法:this.$worker.run(func, [args]?)
写法如下:
html
<h1> 开启一个线程运算用 $worker.run 办法 </h1>
<br />
<el-button
@click="openOneThread"
type="success"
plain
size="small"
style="margin-bottom: 16px"
> 计算斐波那契数列值和用时,以及渲染页面两个工作 </el-button
>
<div>
斐波那契值为:<span class="bold">{{fibRes}}</span>
<i v-show="btnLoading" class="el-icon-loading"></i>
执行用时:<i v-show="btnLoading" class="el-icon-loading"></i>
<span class="bold">{{fibTime}}</span>
毫秒
</div>
js
// data
worker: null,
btnLoading: false,
fibNum: 44,
fibRes: null, // 斐波那契计算的后果
fibTime: null, // 斐波那契计算用时
// methods
openOneThread() {
/* 第一个 UI 操作 */
this.btnLoading = true;
this.worker = this.$worker // this.$worker.run(func, [args]?) 一次性的,主动销毁
.run((n) => {
// 留神这里的函数是外部另一个线程空间的,不能从内部引入过去(内存空间隔离)function fib(n) {if ((n == 1) | (n == 2)) {return 1;} else {return fib(n - 1) + fib(n - 2);
}
}
let start = Date.now(); // console.time()和 console.timeEnd()间接拿不到值,就换种形式
let res = fib(n);
let end = Date.now(); // window.performance.now()也不能用,因为没 window 对象
return [res, end - start]; // 返回数组,第一项是 fib(44)的值,第二项是 fib(44)递归计算用时
},
[this.fibNum] // 参数,从这里传递进去,数组模式
)
.then((res) => {console.log("then", res); // 另一个线程执行完当前就能拿到后果了
this.fibRes = res[0];
this.fibTime = res[1];
this.btnLoading = false;
})
.catch((err) => {console.log("err", err);
this.btnLoading = false;
});
/* 第二个 UI 操作 */
let h2Dom = this.$refs.renderH2;
let n = 0;
let timer = setInterval(() => {if (n >= 60) {clearInterval(timer);
} else {
n++;
h2Dom.innerHTML = n;
}
}, 100);
// 应用 web worker 插件 vue-work 确实能够做到不阻塞
},
效果图
案例二 开启三个辅助线程进行计算看工夫
案例一,曾经能够看到 Web Worker
的劣势
了。接下来,咱们再举个例子。
需要:须要计算 3 个 fib(30)
,如果咱们应用Promise
,写法是这样的:
Promise.all 进行计算
async usePromiseFn() {function asyncOne() {let async1 = new Promise(async (resolve, reject) => {function fib(n) {if ((n === 1) | (n === 2)) {return 1;} else {return fib(n - 1) + fib(n - 1);
}
}
resolve(fib(30));
});
return async1;
}
function asyncTwo() {let async2 = new Promise(async (resolve, reject) => {function fib(n) {if ((n === 1) | (n === 2)) {return 1;} else {return fib(n - 1) + fib(n - 1);
}
}
resolve(fib(30));
});
return async2;
}
function asyncThree() {let async3 = new Promise(async (resolve, reject) => {function fib(n) {if ((n === 1) | (n === 2)) {return 1;} else {return fib(n - 1) + fib(n - 1);
}
}
resolve(fib(30));
});
return async3;
}
console.time("应用 Promise 搭配 aysnc 和 await");
// let paramsArr = [asyncOne()]; // 计算一个大略在 3 秒左右(计算一次刷新一次页面,准确一些)let paramsArr = [asyncOne(), asyncTwo(), asyncThree()]; // 计算三个耗时工作大略在 9 秒左右
let res = await Promise.all(paramsArr);
console.timeEnd("应用 Promise 搭配 aysnc 和 await");
console.log("后果", res);
},
},
应用 Promise.all
办法去计算 3 个 fib 值
,用时在 9 秒左右
,确实是有些慢。那么,咱们应用Web Worker
形式呢?
此外,应用 Promise.all
控制台也会有正告揭示:[Violation] 'click' handler took 8822ms
意思是:
click
事件中执行的程序耗时过长
,看到没,如果应用js
的主线程
去进行简单计算,浏览器都看不下去了 …
再一个,大家在 Promise 执行
的时候,去选中页面上的文字,发现选中不了!就像卡住了一样!
从这个方面也阐明,js 主线程
不适宜执行简单的运算,阻塞...
Web Worker 进行计算
这里应用 this.$worker.create
办法,搭配 this.worker2.postAll
办法
代码写法如下
created() {
// 1. 定义线程所用的音讯函数数组
const actions = [
{
message: "fn1", // message 音讯与 func 函数执行为映射关系
func: (params1, params2) => {console.log("params 参数 -->", params1, params2);
function fib(n) {if ((n == 1) | (n == 2)) {return 1;} else {return fib(n - 1) + fib(n - 2);
}
}
return fib(30);
},
},
{
message: "fn2",
func: () => {function fib(n) {if ((n == 1) | (n == 2)) {return 1;} else {return fib(n - 1) + fib(n - 2);
}
}
return fib(30);
},
},
{
message: "fn3",
func: () => {function fib(n) {if ((n == 1) | (n == 2)) {return 1;} else {return fib(n - 1) + fib(n - 2);
}
}
// throw "一个报错挂了, 其余的也跟着挂了, 走.catch"; // 抛出谬误(确实很像 Promise.all())
return fib(30);
},
},
];
// 2. 依据音讯函数数组去 create 创立一个 worker,并存到 data 变量中去,以便于后续随时应用
this.worker2 = this.$worker.create(actions);
},
// 点击触发 noParamsFn 办法执行
// 应用多个线程
noParamsFn() {
this.loadingOne = true;
console.time("多个线程计算用时 1");
this.worker2
.postAll()
.then((res) => {console.timeEnd("多个线程计算用时 1");
console.log("res", res); // 后果是一个数组 [267914296, 433494437, 701408733]
this.loadingOne = false;
})
.catch((err) => {console.timeEnd("多个线程计算用时 1");
console.log("err", err);
this.loadingOne = false;
});
},
咱们看一下效果图
看到没有。只用了53 毫秒
!
- 应用主线程去计算
3 个 fib(30)
的值,须要将近 9 秒
的工夫 - 而应用
Web Worker
去创立三个辅助线程别离去运算fib(30)
所须要的工夫,只须要50 多毫秒
性能晋升了 100 多倍!
这个案例才真正的体现了,Web Worker
开启多线程运算提高效率的弱小!
大家能够去笔者的集体网站下来看残缺的效果图:http://ashuai.work:8888/#/myWork
Web Worker
是HTML5
提出的标准
,支流浏览器都曾经失去了兼容。IE
就疏忽吧
附上案例残缺代码
篇幅无限,一些 vue-worker
插件罕用的应用语法细节,写在代码中了,大家复制粘贴即可应用
<template>
<div class="threadWrap">
<h1> 开启一个线程运算用 $worker.run 办法 </h1>
<br />
<el-button
@click="openOneThread"
type="success"
plain
size="small"
style="margin-bottom: 16px"
> 计算斐波那契数列值和用时,以及渲染页面两个工作 </el-button
>
<div>
斐波那契值为:<span class="bold">{{fibRes}}</span>
<i v-show="btnLoading" class="el-icon-loading"></i>
执行用时:<i v-show="btnLoading" class="el-icon-loading"></i>
<span class="bold">{{fibTime}}</span>
毫秒
</div>
<br />
<div class="UI">
<span> 不阻塞后续的代码执行:</span>
<h2 ref="renderH2"></h2>
</div>
<br />
<br />
<h1> 开启多个线程应用 $worker.create、postAll 办法 </h1>
<br />
<el-button
@click="noParamsFn"
type="success"
plain
size="small"
style="margin-bottom: 12px"
:loading="loadingOne"
> 不传参都执行一次 </el-button
>
<el-button
@click="byMessageNameStrFn"
type="success"
plain
size="small"
style="margin-bottom: 12px"
:loading="loadingTwo"
> 依据 message 的名字指定谁执行(字符串模式)</el-button
>
<el-button
@click="byMessageNameObjParamsFn"
type="success"
plain
size="small"
style="margin-bottom: 12px"
:loading="loadingThree"
> 依据 message 的名字指定谁执行(对象模式可传参)</el-button
>
<div class="info">F12 请关上控制台查看 --></div>
<br />
<h1> 不应用多线程,应用 Promise.all 太耗时啦!</h1>
<br />
<el-button
@click="usePromiseFn"
type="success"
plain
size="small"
style="margin-bottom: 12px"
>Promise 执行多个工作 </el-button
>
<div class="info">F12 请关上控制台查看 --></div>
</div>
</template>
<script>
export default {
name: "myWorkName",
data() {
return {
worker: null,
btnLoading: false,
fibNum: 44,
fibRes: null,
fibTime: null,
/****/
loadingOne: false,
loadingTwo: false,
loadingThree: false,
worker2: null,
};
},
methods: {
/**
* 需要:点击数据计算进行两个操作
* 第一个 UI 操作,计算斐波那契数 fib(44)的值,且统计计算所用的时长,并写入到页面上
* 第二个 UI 操作,每隔 0.1 秒,更新页面上 h2 标签的内容值
* 要求两个操作不阻塞,不能呈现后一个 UI 操作要期待前一个 UI 操作的状况
* 因为斐波那契是递归执行,是一个比拟耗时的操作 fib(44)约须要近 8 秒的计算工夫
* */
openOneThread() {
/* 第一个 UI 操作 */
this.btnLoading = true;
this.worker = this.$worker // this.$worker.run(func, [args]?) 一次性的,主动销毁
.run((n) => {
// 留神这里的函数是外部另一个线程空间的,不能从内部引入过去(内存空间隔离)function fib(n) {if ((n == 1) | (n == 2)) {return 1;} else {return fib(n - 1) + fib(n - 2);
}
}
let start = Date.now(); // console.time()和 console.timeEnd()间接拿不到值,就换种形式
let res = fib(n);
let end = Date.now(); // window.performance.now()也不能用,因为没 window 对象
return [res, end - start]; // 返回数组,第一项是 fib(44)的值,第二项是 fib(44)递归计算用时
},
[this.fibNum] // 参数,从这里传递进去,数组模式
)
.then((res) => {console.log("then", res); // 另一个线程执行完当前就能拿到后果了
this.fibRes = res[0];
this.fibTime = res[1];
this.btnLoading = false;
})
.catch((err) => {console.log("err", err);
this.btnLoading = false;
});
/* 第二个 UI 操作 */
let h2Dom = this.$refs.renderH2;
let n = 0;
let timer = setInterval(() => {if (n >= 60) {clearInterval(timer);
} else {
n++;
h2Dom.innerHTML = n;
}
}, 100);
// 应用 web worker 插件 vue-work 确实能够做到不阻塞
},
/**
* 应用多个线程
* */
// 调用形式一 不传参
noParamsFn() {
this.loadingOne = true;
console.time("多个线程计算用时 1");
this.worker2
.postAll()
.then((res) => {console.timeEnd("多个线程计算用时 1");
console.log("res", res); // 后果是一个数组 [267914296, 433494437, 701408733]
this.loadingOne = false;
})
.catch((err) => {console.timeEnd("多个线程计算用时 1");
console.log("err", err);
this.loadingOne = false;
});
},
// 调用形式二 依据 message 的名字去指定谁(可多个)去执行 字符串模式
byMessageNameStrFn() {
this.loadingTwo = true;
console.time("多个线程计算用时 2");
this.worker2
.postAll(["fn1", "fn3"]) // 这里指定 "fn1", "fn3" 去执行
.then((res) => {console.timeEnd("多个线程计算用时 2");
console.log("res", res); // 后果是一个数组 [267914296, 701408733]
this.loadingTwo = false;
})
.catch((err) => {console.timeEnd("多个线程计算用时 2");
console.log("err", err);
this.loadingTwo = false;
});
},
// 调用形式三 依据 message 的名字去指定谁(可多个)去执行 对象模式
byMessageNameObjParamsFn() {
this.loadingThree = true;
console.time("多个线程计算用时 3");
this.worker2
.postAll([{message: "fn1", args: ["代码修仙路漫漫", "加油干"] }]) // 这里指定 "fn1" 去执行
.then((res) => {console.timeEnd("多个线程计算用时 3");
console.log("res", res); // 后果是一个数组 []
this.loadingThree = false;
})
.catch((err) => {console.timeEnd("多个线程计算用时 3");
console.log("err", err);
this.loadingThree = false;
});
},
/**
* 应用 Promise
* */
async usePromiseFn() {function asyncOne() {let async1 = new Promise(async (resolve, reject) => {function fib(n) {if ((n === 1) | (n === 2)) {return 1;} else {return fib(n - 1) + fib(n - 1);
}
}
resolve(fib(30));
});
return async1;
}
function asyncTwo() {let async2 = new Promise(async (resolve, reject) => {function fib(n) {if ((n === 1) | (n === 2)) {return 1;} else {return fib(n - 1) + fib(n - 1);
}
}
resolve(fib(30));
});
return async2;
}
function asyncThree() {let async3 = new Promise(async (resolve, reject) => {function fib(n) {if ((n === 1) | (n === 2)) {return 1;} else {return fib(n - 1) + fib(n - 1);
}
}
resolve(fib(30));
});
return async3;
}
console.time("应用 Promise 搭配 aysnc 和 await");
// let paramsArr = [asyncOne()]; // 计算一个大略在 3 秒左右(计算一次刷新一次页面,准确一些)let paramsArr = [asyncOne(), asyncTwo(), asyncThree()]; // 计算三个耗时工作大略在 9 秒左右
let res = await Promise.all(paramsArr);
console.timeEnd("应用 Promise 搭配 aysnc 和 await");
console.log("后果", res);
},
},
created() {
// 1. 定义线程所用的音讯函数数组
const actions = [
{
message: "fn1", // message 音讯与 func 函数执行为映射关系
func: (params1, params2) => {console.log("params 参数 -->", params1, params2);
function fib(n) {if ((n == 1) | (n == 2)) {return 1;} else {return fib(n - 1) + fib(n - 2);
}
}
return fib(30);
},
},
{
message: "fn2",
func: () => {function fib(n) {if ((n == 1) | (n == 2)) {return 1;} else {return fib(n - 1) + fib(n - 2);
}
}
return fib(30);
},
},
{
message: "fn3",
func: () => {function fib(n) {if ((n == 1) | (n == 2)) {return 1;} else {return fib(n - 1) + fib(n - 2);
}
}
// throw "一个报错挂了, 其余的也跟着挂了, 走.catch"; // 抛出谬误(确实很像 Promise.all())
return fib(30);
},
},
];
// 2. 依据音讯函数数组去 create 创立一个 worker,并存到 data 变量中去,以便于后续随时应用
this.worker2 = this.$worker.create(actions);
},
// 别忘了在组件销毁前清除掉哦
beforeDestroy() {this.worker = null;},
};
</script>
<style lang='less' scoped>
.bold {
font-weight: 700;
font-size: 24px;
}
.info {
color: #999;
font-size: 13px;
}
.UI {
display: flex;
align-items: center;
}
</style>
或者大家去笔者的 GitHub 仓库
中拉取代码,跑起来看,更加不便了解。
GitHub 仓库
地址:https://github.com/shuirongsh…
js 中统计代码执行时长的三种形式
附带 js
中罕用的 统计程序执行时长
的三种形式
- window.performance.now()
- https://developer.mozilla.org…
// 计算工夫形式一
function fib(n) {if (n === 1 | n === 2) {return 1} else {return fib(n - 1) + fib(n - 2)
}
}
let start = window.performance.now() // 单位毫秒
fib(40)
let end = window.performance.now() // 单位毫秒
console.log((end - start).toFixed(0) + '毫秒');
- Date.now()
- https://developer.mozilla.org…
// 计算工夫形式二
function fib(n) {if (n === 1 | n === 2) {return 1} else {return fib(n - 1) + fib(n - 2)
}
}
let start = Date.now() // 单位毫秒
fib(40)
let end = Date.now() // 单位毫秒
console.log((end - start).toFixed(0) + '毫秒');
- console.time() & console.timeEnd()
- https://developer.mozilla.org…
console.time('tom')
console.timeEnd('tom')
A good memory is not as good as a bad pen, record it...