Skip to content

我们知道 JS 是一个单线程语言,什么是单线程语言呢,实际上就是语言本身并没有实现多线程的语法。

尽管 JS 并没有提供多线程的语法,但是不管是浏览器和 nodejs 环境中,都提供了不同程度的多线程支持,注意,这种多线程支持并不是 JS 语言本身提供的,而是浏览器和 nodejs 这种宿主环境中提供的,在浏览器中提供的多线程支持就是 worker,我们可以认为 worker 实际上就是一个线程。

我们举个例子,假设我页面中有一个 JS 动画,这个 JS 动画是需要实时计算的,但是如果此时我因为触发了某些事件,进行了一个比较大型的数据运算,此时 JS 线程就会暂时阻塞住,也就是 JS 动画会被卡住,这其实并不是我们想要的效果。

当我们创建一个 Worker 对象的时候,传入一个 JS 文件的路径,就开启了一个线程

js
const worder = new Worker("./test.js");

当创建一个 worker 对象的时候,会自动执行里面的代码

我们还可以传入第二个参数,第二个参数为配置对象,有三个属性

  • type:如果设置为 "module",那么表示这个文件应该支持 ES 6 模块化,默认为 "classic",此时如果在文件内写了 ES 6 模块化的代码会直接报错
  • credentials:在 worker 里面发送请求的时候的凭证携带,same-origin 是携带同源,include 是携带所有,默认值为 omit,即不携带凭证。
  • name:给他取个名,只和窗口调试有关,可以在控制台看到,没什么用

我们想一想,我们要开启一个线程,无非就是让他帮忙干一些事情,并且我们还应该考虑到,我们也需要知道他什么时候干完的,这是一个异步场景,这里是使用事件的形式进行通知的,也就是说当 worker 执行完了一个任务之后,会触发某个事件。

首先我们要考虑的是如何让 worker 线程帮我们干一些事,我们可以通过 worker 对象上的 postMessage() 函数,当我们调用这个函数的时候,就相当于通知 worker 帮我们做一些事。

接下来是在 worker 文件的内部,内部有一个 self 对象,这个对象上有一个 message 事件,当我们绑定这个事件之后,当外部调用 postMessage 的时候,就会触发该事件,换句话说,当我们调用 worker 对象上的 postMessage 函数的时候,实际上是在触发 worker 文件内 self 对象的 message 事件,并且,message 事件对象上的 data 方法就可以拿到 postMessage 函数传入的参数。

js
const wk = new Worker("./test.js");//创建一个worker
wk.postMessage("abc");//调用worker的postMessage函数,触发worker文件的message事件,并传入了数据
js
//./test.js
self.addEventListener("message", (e) => {
    //文件内绑定message事件
    console.log(e.data); //e.data保存了传入的数据
});

接下来一个问题是,我如何知道 self 的 message 事件执行完成了,实际上 self 和 worker 对象是很类似的,self 上面也可以调用 postMessage 方法,并传入参数,而在外部可以给 worker 对象绑定一个 message 事件,此时调用 self 的 postMessage 方法,就会触发 worker 对象的 message 事件。

我们可以认为,创建一个 worker 对象,实际上就是创建一个新的线程,但是这个线程里面的代码是要在一个文件内,在这个文件内有一个 self 对象,worker 对象和 self 对象可以互相通信,self 对象可以调用 worker 的 message 事件,worker 可以调用 self 的 message 事件,这样就完成了通信这一操作,同时这个新线程执行的时候是放到一个新线程的,所以不会影响主线程的运行,所以对于那些非常耗时的操作,我们都会考虑新开一个线程去做。

js
const wk = new Worker("./test.js");//创建一个worker
wk.postMessage("abc");//调用worker的postMessage函数,触发worker文件的message事件,并传入了数据
wk.addEventListener("message", e => {//绑定message事件
	console.log(e.data);
});
js
//./test.js
self.addEventListener("message", (e) => {
    //文件内绑定message事件
    console.log(e.data); //e.data保存了传入的数据(如果是多个数据,则是一个数组)
    const data = "我是一个返回的数据";
    self.postMessage(data); //执行完毕,调用postMessage方法,将数据返回给调用者
});

我们可以使用 worker 对象的 terminate 方法来终止线程

这个 worker 对象是浏览器给我们提供的用于实现多线程的方式,但是线程并不是无脑的越多越快,我们可以使用 navigator.hardwareConcurrency 来访问当前系统 CPU 的逻辑线程数量,将线程数量提高到逻辑线程的数量可以获得最高的性能。

以笔记为镜,可以知得失