JavaScript异步编程之分割耗时任务中,我们通过定时器把耗时任务分割成多个部分,从而避免了耗时任务阻塞线程,进而阻塞UI。

好在HTML5为我们提供了「web worker」,使得我们可以在另一个线程中运行JavaScript。使用「web worker」运行耗时任务比使用定时器要高效优雅地多。

从下图可以看出,「web worker」的兼容性还是很好的,只是IE又拖后腿了…

web worker兼容性

为了兼容不支持的浏览器,我们需要为不支持「web worker」的浏览器提供后备方案:使用定时器分割耗时任务。

1
2
3
4
5
if ( Worker ) {
  // 使用web worker
} else {
  // 使用定时器
}

创建worker

一个worker实例通过给全局构造函数Worker传递需要使用worker线程中运行的脚本的URL创建。传入的URL需要满足同源策略。

1
const worker = new Worker('worker.js');

受限的worker

worker线程在很多方面受限。

worker线程中的脚本无法获取和修改DOM,这避免了主线程和worker线程在修改DOM上产生竞态。

worker线程中的脚本无法获取window全局变量,而是拥有自己的全局变量self,在它的上面绑定了一些标准JavaScript对象和方法,如setTimeoutMathXMLHttpRequest(不过responseXMLchannel属性始终是null)和WebSocket(如果浏览器支持的话)等。

worker线程和主线程完全处于两个独立的作用域中,主线程和worker线程之间只能通过消息交换数据,而且数据是以复制的形式而不是赋值的形式传递。

worker线程和主线程之间的消息传递

主线程和worker线程之间通过消息机制交换数据:传递数据使用postMessage方法,获取数据监听message事件。

1
2
3
4
5
6
7
8
// main.js
const worker = new Worker('worker.js');

worker.postMessage('foo');

worker.addEventListener('message', function(e) {
  // e.data的值就是传递的数据
});
1
2
3
4
5
6
// worker.js
self.addEventListener('message', function(e) {
  // e.data的值就是传递的数据
});

self.postMessage('bar');

message事件对象的data属性的值就是传递的数据。

主线程和worker线程之间数据的传递使用复制的方式,这避免了对传递的数据的修改对原线程的值产生的影响。

示例

下面的示例把JavaScript异步编程之分割耗时任务中的耗时任务使用「web worker」运行。

See the Pen run long-running task with web worker by xingzhi (@xingzhi) on CodePen.