什么是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 workervar 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官网上看看这个插件的下载量如何,以及GitHubstar数量多少。

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>  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 执行用时:  <i v-show="btnLoading" class="el-icon-loading"></i>  <span class="bold">{{ fibTime }}</span>  毫秒</div>

js

// dataworker: null,btnLoading: false,fibNum: 44,fibRes: null, // 斐波那契计算的后果fibTime: null, // 斐波那契计算用时// methodsopenOneThread() {  /* 第一个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 WorkerHTML5提出的标准,支流浏览器都曾经失去了兼容。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>      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 执行用时:      <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...