Web Worker学习笔记

Web worker的存在,为JavaScript提供了多线程的运行环境,允许主线程创建worker线程。

此文档主要介绍的是Dedicated Worker

背景

JavaScript语言设计之初采用的是单线程的模型。所有的任务都是在一个线程上通过回调来进行调度的。前面的任务没做完,后面的任务就只能等着。随着计算机算力的增强,尤其是多核CPU的出现,单线程限制了计算机算力的发挥。

作用

Web worker的存在,为JavaScript提供了多线程的运行环境,允许主线程创建worker线程,并将一些任务分配给后者。在主线程运行的同时,worker线程在后台运行,二者互不干扰。等到Worker线程完成运算后,再将运算结果返回给主线程。

优势:计算密集型或者高延迟的任务由Worker来进行,不会给主线程(负责UI与交互)造成负担(阻塞或者拖慢),增强用户体验。

缺点:Worker线程一旦创建成果,就会始终运行,不会被主线程的交互打断。这样有利于随时相应主线程的通信,但是也会浪费一定的资源,不应被过度使用。而且,一旦使用完毕,就应该尽快关闭它。

特点

  • 同源限制

    分配给Worker线程执行的脚本文件,必须与主线程的脚本同源。

  • DOM限制

    Worker线程所在的全局对象,与主线程不同,无法读取主线程所在网页上的DOM对象。也无法使用windowdocumentparent这些对象。但是Worker线程可以使用NavigatorLocation对象。

  • 通信联系

    Worker线程与主线程不在同一个上下文环境,它们之间不能直接通信,必须通过消息来完成。

  • 脚本显示

    Worker线程不能执行alert()confirm()方法,但可以使用XMLHTTPRequest对象来完成Ajax请求。

  • 文件限制

    Worker线程无法读取本地文件,也就是说它无法打开本机的文件系统(file://),它加载的脚本,必须来自于网络。

用法

主线程

创建

主线程中采用Worker构造函数来新建Worker进程,它接收一个参数,就是脚本文件(且只能加载js脚本,否则报错),即是Worker线程即将执行的任务。这个文件必须来自网络。如果加载不成功,它会静默失败

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

第二个参数是可选项,用于配置,比如name属性可以用来自定义worker的名字,可在worker内使用self.name访问到。

发送消息

然后主线程调用worker.postMessage()方法给Worker进程发送消息。

1
2
3
4
5
worker.postMessage('Hello world');
worker.postMessage({
method: 'echo',
args: ['work']
});

worker可以接收包括二进制在内的各种数据类型的消息内容。

接收消息

主线程可以通过worker.onmessage()来指定监听函数,接收Worker线程发送回来的消息。

1
2
3
4
5
6
7
8
9
worker.onmessage = function (event) {
console.log('Received message ' + event.data);
doSomething();
};

function doSomething () {
// 执行任务
worker.postMessage('Work done!');
}

Worker线程发送给主线程的数据被保存在事件对象eventdata字段上。

关闭Worker

Worker线程任务完成后,主线程就可以把它关掉。

1
worker.terminate();

错误监听

1
2
3
4
5
6
7
8
9
worker.onerror(function (err) {
console.log([
'ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message
].join(''));
});

worker.addEventListner('error', function (evt) {
// doSomething
}, !1);

Worker线程

Worker的内部可以再新建worker

消息处理

Worker线程内部需要有一个事件监听函数,来监听message事件。

1
2
3
self.addEventListener('message', function (e) {
self.postMessage('You said ' + e.data);
}, !1);

代码中self代表Worker线程自身,即子线程的全局对象。也可以写作this或者省略。

也可以直接使用self.onmessage来指定事件监听函数。

根据主线程发来的数据的不同,Worker线程可以执行不同的任务来调用不同的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
self.addEventListener('message', function (e) {
var data = e.data;
switch (data.cmd) {
case 'start':
self.postMessage('WORKER SATRTED' + data.msg);
break;
case 'stop':
self.postMessage('WORKER STOPED' + data.msg);
self.close(); // Terminates worker
break;
default:
self.postMessage('Unknown CMD' + data.msg);
}
}, !1);

self.close()用于在Worker线程内关闭自身。

加载脚本

Worker线程内部如果要加载其他脚本,有一个专门的方法importScripts

1
2
3
4
5
// 加载单个脚本
importScripts('worker1.js');

// 加载多个脚本
importScripts('worker1.js', 'worker2.js');

错误监听

Worker线程内部可以监听error事件,来捕获其内发生的错误。


同页面的Worker

通常情况下,我们会将Worker线程的代码写入一个单独的JavaScript文件中,但是我们也可以载入和主线程在同一个网页的代码。

1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<body>
<script id="worker" type="app/worker">
addEventListener('message', function () {
postMessage('some message');
}, false);
</script>
</body>
</html>

注意需要将Script标签的type值设置为浏览器无法识别的一个值。

再读取这段脚本,用Worker来处理。

先将嵌入网页的脚本代码,转换成一个二进制对象,然后为这个二进制对象生成了URL,再让Worker加载这个URL。

1
2
3
4
5
6
7
var blob = new Blob([document.querySelector('#worker').textContent]);
var url = window.URL.createObjectURL(blob);
var worker = new Worker(url);

worker.onmessage = function (e) {
// e.data === 'some message'
};

参考资料

Web Worker 使用教程

有钱,任性!!!