部分增强Web技术(3)
三、Web Worker
1.概述
JavaScript语言采用的是单线程模型,也就是说,所有任务只能在一个线程上完成,一次只能做一件事。前面的任务没做完,后面的任务只能等着。随着电脑计算能力的增强,尤其是多核 CPU 的出现,单线程带来很大的不便,无法充分发挥计算机的计算能力。
Web Worker的作用,就是为JavaScript创造多线程环境,允许主线程创建Worker线程,将一些任务分配给后者运行。在主线程运行的同时,Worker线程在后台运行,两者互不干扰。等到Worker线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务,被Worker线程负担了,主线程(通常负责UI交互)就会很流畅,不会被阻塞或拖慢。
Worker线程一旦新建成功,就会始终运行,不会被主线程上的活动(比如用户点击按钮、提交表单)打断。这样有利于随时响应主线程的通信。但是,这也造成了Worker比较耗费资源,不应该过度使用,而且一旦使用完毕,就应该关闭。
2.注意
Web Worker有以下几个使用注意点:
(1)同源限制。分配给Worker线程运行的脚本文件,必须与主线程的脚本文件同源(同协议、同域名、同端口)。
(2)DOM 限制。Worker线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的DOM对象,也无法使用document、window、parent这些对象。但是,Worker线程可以使用navigator对象和location对象。
(3)脚本限制。Worker 线程不能执行alert()方法和confirm()方法,但可以使用XMLHttpRequest对象发出AJAX请求。
(4)文件限制。Worker线程无法读取本地文件,即不能打开本机的文件系统(file://),它所加载的脚本,必须来自网络。
(5)通信联系。Worker 线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息完成。
3.基本用法
(1)主线程
主线程采用new命令,调用Worker()构造函数,新建一个Worker线程。
var worker = new Worker('...work.js');
Worker()构造函数的参数是一个脚本文件,该文件就是Worker线程所要执行的任务。由于Worker不能读取本地文件,所以这个脚本必须来自网络。如果下载没有成功(比如404错误),Worker线程的任务就会无法执行。
然后,主线程调用worker.postMessage()方法,向Worker发消息。这个方法的参数就是主线程传给Worker的数据,可以是各种数据类型,包括二进制数据,如:
worker.postMessage('Hello World');
worker.postMessage({method: 'echo', args: ['Work']});
接着,主线程通过worker.onmessage指定监听函数,接收子线程发回来的消息,事件对象的data属性可以获取Worker发来的数据:
worker.onmessage = function(event){
console.log('Received message ' + event.data);
}
Worker完成任务以后,主线程就可以通过worker.terminate();把它关掉,节约资源。
(2)Worker线程
Worker线程内部需要有一个监听函数,监听message事件:
self.addEventListener('message', function(event){
self.postMessage('You said: ' + event.data);
}, false);
除了使用self.addEventListener()指定监听函数,还可以使用self.onmessage指定。监听函数的参数是一个事件对象,它的data属性包含主线程发来的数据。self.postMessage()方法用来向主线程发送消息。
Worker同样可以加载其它脚本完成任务(相当于子线程里的子线程),Worker内部如果要加载其他脚本,有一个专门的方法importScripts(),如:
importScripts('...script1.js');
主线程可以监听Worker是否发生错误(Worker内部也可以监听error事件)。如果发生错误,Worker会触发主线程的error事件:
worker.onerror(function(event){
console.log(['ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message].join(''));
});
// 或者
worker.addEventListener('error', function(event){
// ...
});
使用完毕,为了节约资源,需要关闭子线程:
self.close();
附实例代码:
<!-- 主线程部分 --> <script> //由于 Worker 不能读取本地文件,所以这个脚本必须来自网络 //var worker = new Worker('js/work.js'); var worker =new Worker('http://39.96.14.133/web1903_6html5API/js/work.js'); //1.主进程推送给子进程 可以使用对象或者字符串等 worker.postMessage({method: 'echo', args: ['Work']}); //worker.postMessage('Hello World'); //4.接受子进程过来的数据 worker.onmessage = function (event) { console.log('Received message ' + event.data); } //5.使用完毕,为了节省系统资源,必须关闭 Worker worker.terminate(); </script> <!-- 子线程部分 --> //2.把数处理后的程序发送给主进程 self.addEventListener('message', function (e) { //3.子进程处理传入的参数 self.postMessage('You said: ' + e.data); //e.data 就是传入的数据 }, false); //work 进程识别主进程的不同处理 // self.addEventListener('message', function (e) { // var data = e.data; // switch (data.cmd) { // case 'start': // self.postMessage('WORKER STARTED: ' + data.msg); // break; // case 'stop': // self.postMessage('WORKER STOPPED: ' + data.msg); // self.close(); // Terminates the worker. // break; // default: // self.postMessage('Unknown command: ' + data.msg); // }; // }, false); //5.使用完毕,为了节省系统资源,必须关闭 Worker self.close();