JavaScript 语言采用的是单线程模型,也就是说,所有任务只能在一个线程上完成,一次只能做一件事。前面的任务没做完,后面的任务只能等着。随着电脑计算能力的增强,尤其是多核 CPU 的出现,单线程带来很大的不便,无法充分发挥计算机的计算能力。
一、简述
Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。
在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务,被 Worker 线程负担了,主线程(通常负责 UI 交互)就会很流畅,不会被阻塞或拖慢。
Worker 线程一旦新建成功,就会始终运行,不会被主线程上的活动(比如用户点击按钮、提交表单)打断。这样有利于随时响应主线程的通信。但是,这也造成了 Worker 比较耗费资源,不应该过度使用,而且一旦使用完毕,就应该关闭。
二、注意点
Web Worker 有以下几个使用注意点:
- 同源限制
分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。(所谓同源是指,域名,协议,端口相同。) - DOM 限制
Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用document、window、parent这些对象。但是,Worker 线程可以navigator对象和location对象。 - 通信联系
Worker 线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息完成。 - 脚本限制
Worker 线程不能执行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 对象发出 AJAX 请求。 - 文件限制
Worker 线程无法读取本地文件,即不能打开本机的文件系统(file://),它所加载的脚本,必须来自网络。
三、基础用法
3.1 主线程
用new命令,Worker()构造函数,创建一个Worker线程
// main.js
var myworker = new Worker('work.js');
注意work.js必须是跑在服务器下的一个文件。**(后面有教不用跑服务也可以启动worker的方法)**大家可以简单用anywhere跑起一个服务。其他关于新线程的逻辑都写在worker.js里面。
主线程-传递信息
主线程是通过myworker.postMessage()
方法向子线程传递消息
myworker.postMessage('hello world')
myworker.postMessage({name: 'charming'})
worker.postMessage()
方法的参数,就是主线程传给 Worker 的数据。它可以是各种数据类型,包括二进制数据。
主线程-接收信息/监听事件
主线程是通过myworker.onmessage()
方法监听子线程发回来的消息
myworker.onmessage = function (event) {
console.log('接收到子线程的消息:' + event.data)
}
主线程-终止子线程
myworker.terminate()
3.2 子线程
子线程代码都写在work.js里面
子线程-接收消息
子线程内部通过监听message
接收主线程传递过来的信息。
self.addEventListener('message', function (e) {
self.postMessage('从主线程接收到: ' + e.data);
}, false);
子线程-代码写法
在上面代码中,self表示子线程自身。即子线程的全局对象。等同于厦门两种写法
this.addEventListener('message', function (e) {
this.postMessage('从主线程接收到: ' + e.data);
}, false);
addEventListener('message', function (e) {
postMessage('从主线程接收到: ' + e.data);
}, false);
当然直接用onmessage监听message事件也是可以的
onmessage = function(e) {
postMessage('从主线程接收到: ' + e.data);
}
子线程-传递信息
一样是postMessage方法。
ps可以用对象传递数据。如{ method: 'start', data: 'now start' }
来区分不同的事件。
子线程-终止子线程
self.close()
用于在子线程内部关闭自身。
3.3 加载脚本
子线程内部如果要加载其他脚本,有一个专门的方法importScripts()
importScripts('script1.js');
// 该方法可以同时加载多个脚本。
importScripts('script1.js', 'script2.js');
3.4 错误处理
主线程可以监听子线程是否发生错误。如果发生错误,子线程会触发主线程的error事件。
myworker.onerror(function (event) {
console.log([
'ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message
].join(''));
});
// 或者
myworker.addEventListener('error', function (event) {
// ...
});
四、新建worker不用运行在服务器上
通常情况下,子线程载入的是一个单独的 JavaScript 脚本文件,但是也可以载入与主线程在同一个网页的代码。
<!DOCTYPE html>
<body>
<script id="worker" type="app/worker">
addEventListener('message', function () {
postMessage('some message');
}, false);
</script>
</body>
</html>
上面是一段嵌入网页的脚本,注意必须指定<script>
标签的type
属性是一个浏览器不认识的值,上例是app/worker
。
然后,读取这一段嵌入页面的脚本,用 Worker 来处理。
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'
};
五、实际应用场景
- 比较常见的是把计算丢给子线程,然后子线程完成之后再通知主线程更新。
- 让子线程完成轮询。