Mucho tiempo sin verte, alguien que ha desaparecido por mucho tiempo 靓仔
está de vuelta. En cuanto a por qué este problema no ha ocurrido durante tanto tiempo, usemos el ajetreo como excusa. emmm, no, 忙
¡ 忙昏头了
es del tipo real! ! !
Está bien, no más tonterías.
Por lo general, mis amigos no dicen eso en el desarrollo diario del proyecto, simplemente están moviendo ladrillos y no hay puntos brillantes, ¡así que vamos ahora!
Hablemos hoy , esto es lo que le gusta web worker
al entrevistador ~最最最
性能优化
¿Por qué JavaScript es de un solo subproceso?
Es bien sabido que el JavaScript
lenguaje se caracteriza por 单线程
, es decir, que solo se puede hacer una cosa a la vez. Entonces, ¿por qué JavaScript
no puede 多个线程
haber? puede 提高效率
_
El hilo único de JavaScript, en relación con su propósito. Como lenguaje de secuencias de comandos del navegador, JavaScript
el uso principal es interactuar con el usuario y manipular el DOM. Esto determina que solo puede ser 单线程
, de lo contrario traerá problemas de sincronización muy complejos. Por ejemplo, suponga JavaScript
que hay dos subprocesos al mismo tiempo, un subproceso agrega contenido a un nodo DOM y el otro subproceso elimina este nodo, ¿qué subproceso debe tomar el navegador?
¿Qué es un trabajador web?
Para aprovechar 多核CPU
la potencia informática, se HTML5
propone un estándar Web Worker
que permite la creación de scripts de JavaScript 多个线程
, pero los subprocesos secundarios están completamente controlados por el subproceso principal y 不得操作DOM
. Por lo tanto, este nuevo estándar no cambia la naturaleza de subproceso único de JavaScript.
En el subproceso de trabajo, aunque la suma del 直接操作dom节点
objeto de la ventana no puede y no puede usarse , las cosas debajo del objeto de la ventana aún pueden usarse, como , etc.默认方法
属性
websocket
indexedDB
workers
和主线程
间的数据传递通过这样的消息机制进行——双方都使用postMessage()
方法发送各自的消息,使用 onmessage 事件
处理函数来响应消息(消息被包含在Message
事件的 data 属性中)。这个过程中数据并不是被共享而是被复制。
关于web worker的兼容性问题,在can i use
中查找一轮后发现,基本目前所有主流的浏览器都支持了,因此放心食用,无需考虑兼容性的问题。
小试牛刀
前面学习了那么多武功秘籍,少侠们,确定不来一展身手吗?
小羽这里简单的写了一个小demo,这个demo的内容就是递归获取斐波那契数列
。会分为单线程
和多线程
模式,然后分别测试运行20次
fb方法所需要的时间。
- 单线程模式:利用for循环直接执行20次fb,统计执行时间
- 多线程模式:利用for循环,创建多个worker线程。并使用promise.all处理这些异步的worker线程,等待所有的worker执行完成后,统计执行时间
<!--
* @Author: xiaoyu
* @Description:
* @Date: 2022-05-08 08:40:54
* @LastEditors: xiaoyu
* @LastEditTime: 2022-06-29 23:19:40
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>web worker</title>
</head>
<script>
const number = 20 // 运行次数
// 多线程测试
function workerTest() {
console.log('%c 开始多线程测试 ', 'color:#fff; background:#00897b ')
const workerList = []
for (let i = 0; i < number; i++) {
const workerItem = new Promise((resolve, reject) => {
const myWorker = new Worker('worker.js')
myWorker.postMessage({
function: 'fb',
data: 43
})
myWorker.onmessage = (e) => {
resolve(e.data)
// 关闭worker线程
myWorker.terminate()
}
})
workerList.push(workerItem)
}
console.time('worker多线程执行时间')
Promise.all(workerList).then(res => {
console.log(res)
console.timeEnd('worker多线程执行时间')
})
}
function singleTest() {
console.log('%c 开始单线程测试 ', 'color:#fff; background:#00897b ')
function fb(n) {
if (n === 1 || n === 2) {
return 1;
}
return fb(n - 1) + fb(n - 2)
}
console.time('单线程执行时间')
for (let i = 0; i < number; i++) {
const res = fb(43)
console.log({
data: res,
name: 'single test'
})
}
console.timeEnd('单线程执行时间')
}
</script>
<body>
<button onclick="singleTest()">单线程测试</button>
<button onclick="workerTest()">多线程测试</button>
</body>
</html>
/*
* @Author: xiaoyu
* @Description: worker 线程
* @Date: 2022-05-08 08:41:30
* @LastEditors: xiaoyu
* @LastEditTime: 2022-06-29 23:17:44
*/
// 方法对象
const funcObj = {
fb: (n) => {
if(n===1 || n ===2){
return 1;
}
return funcObj.fb(n-1) + funcObj.fb(n-2)
}
}
// onmessage事件
onmessage = function(e){
const {data} = e;
const res = funcObj[data.function](data.data)
// 将获取的数据通过postMessage发送到主线程
self.postMessage({
data: res,
name: 'worker test'
})
self.close()
}
打开任务管理器,点击单线程测试按钮进行单线程的测试。可以从下图发现,单线程
的调用时间约为70s
,cpu的调用基本上也就只是两个核心在切换工作,小羽在多次测试后,其实是有多个核心在切换工作,不过单一
时间只有一个核心
是在满载工作(递归获取斐波那契数列)。
同样是打开任务管理器,然后点击多线程测试按钮。此时咱们的cpu
就不再偷懒
了,直接16线程满载运行,只需要7.9s
就完成了20次递归获取斐波那契数列。
咱们简单的计算一下使用web worker
多线程提升效果:(70750-7973)/7973 ≈7.87。即提升了7.87倍
的效率
。当然这是在8核16线程上的电脑上跑了,如果在核心数不同的cpu上这个倍数也是会发生相应的变化
在单页面应用中使用
通过上面的例子,是不是so easy
呀?
好啦,那咱们就算掌握了web worker
的基本使用
方法啦。
但是在react、vue等单页面
应用中,webpack/vite通常会将js代码打包
成一个js文件。因此通过上面的new Worker('worker.js')的方式来新建worker,将会报访问不到
worker.js的错误。
所以,在单页面应用
中,咱们该怎么使用web worker
呢?
方案1:既然webpack/vite会将js的代码打包成一个js文件,那咱们不让它打包不就好了。而单页面应用的工程下,通常都是会有一个public的
静态资源目录
,咱们将worker.js放入其中即可。方案2:webpack4及以下的版本可以使用
worker-loader
方案3:webpack5/vite则可以使用
new Worker(new URL('worker.js', import.meta.url))
的方式
import React from 'react'
export default function WebWorkerTest() {
const handleClick = () => {
const number = 1
const workerList = []
console.log('%c 开始多线程测试 ', 'color:#fff; background:#00897b ')
for (let i = 0; i < number; i++) {
const workerItem = new Promise((resolve, reject) => {
const myWorker = new Worker(new URL('../utils/fb.worker.ts', import.meta.url))
myWorker.postMessage({
function: 'fb',
data: 43
})
myWorker.onmessage = (e) => {
resolve(e.data)
// 关闭worker线程
myWorker.terminate()
}
})
workerList.push(workerItem)
}
console.time('worker多线程执行时间')
Promise.all(workerList).then(res => {
console.log(res)
console.timeEnd('worker多线程执行时间')
})
}
return (
<>
<button onClick={handleClick}>vite/webpack5</button>
</>
)
}
// fb.worker.ts
// 方法对象
const funcObj = {
fb: (n: number): number => {
if (n === 1 || n === 2) {
return 1;
}
return funcObj.fb(n - 1) + funcObj.fb(n - 2);
},
};
// onmessage事件
onmessage = function (e) {
const { data } = e;
const res = funcObj[data.function](data.data);
// 将获取的数据通过postMessage发送到主线程
self.postMessage({
data: res,
name: "worker test",
});
self.close();
};
注意事项
虽然web worker可以调用cpu的多线程,从而提高咱们页面的性能。但是它不是随便使用
的,如果滥用web worker可能不仅不会得到性能的提升,还可能造成性能的损耗
。
举一个简单的小栗子
如果咱们将递归获取斐波那契数列
第n位的方法,将传入参数修改为第2位
,这时候咱们再重跑单线程测试和多线程测试。
结果如下图,咱们可以发现单线程模式
下,获取20次斐波那契数列第二位的时间仅需要1.5ms
,而在多线程
的情况下却需要78ms
。这是为什么呢?因为咱们每次创建worker线程
以及possmessage通信
都是需要损耗
一些性能以及时间的。因此web worker是不可以滥用
的哦,日常开发中,建议在需要消耗比较多
的cpu运算能力
的时候酌情使用。
小结
本文通过了几个简单的小栗子,带大家学习web worker的基本知识,使用方法,以及需要注意的事项。小伙伴们在日常的开发中可以按需尝试哦,让自己的项目中多些亮点,也可以让面试官眼前一亮哦。
如果看这篇文章后,感觉有收获的小伙伴们可以点赞
+关注
哦~
如果想和小羽
交流技术可以加下wx,也欢迎小伙伴们来和小羽唠唠嗑
,嘿嘿~
ps:本文原创,转载请标明出处
- 我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿。