[TOC]
## 概述
JavaScript语言采用的是单线程模型,也就是说,所有任务排成一个队列,一次只能做一件事。随着电脑计算能力的增强,这一点带来很大的不便,无法充分发挥JavaScript的潜力。尤其考虑到,File API允许JavaScript读取本地文件,就更是如此了。
Web Worker的目的,就是为JavaScript创造多线程环境,允许主线程将一些任务分配给子线程。在主线程运行的同时,子线程在后台运行,两者互不干扰。等到子线程完成计算任务,再把结果返回给主线程。因此,每一个子线程就好像一个“工人”(worker),默默地完成自己的工作。
普通的Wek Worker,只能与创造它们的主进程通信。还有另一类Shared worker,能被所有同源的进程获取(比如来自不同的浏览器窗口、iframe窗口和其他Shared worker)。本节不涉及这一类的worker进程。
Web Worker有以下几个特点:
* 同域限制。子线程加载的脚本文件,必须与主线程的脚本文件在同一个域。
* DOM限制。子线程无法读取网页的DOM对象,即document、window、parent这些对象,子线程都无法得到。(但是,navigator对象和location对象可以获得。)
* 脚本限制。子线程无法读取网页的全局变量和函数,也不能执行alert和confirm方法,不过可以执行setInterval和setTimeout,以及使用XMLHttpRequest对象发出AJAX请求。
* 文件限制。子线程无法读取本地文件,即子线程无法打开本机的文件系统(file://),它所加载的脚本,必须来自网络。
使用之前,检查浏览器是否支持这个API。支持的浏览器包括IE10、Firefox (从3.6版本开始)、Safari (从4.0版本开始)、Chrome 和 Opera 11,但是手机浏览器还不支持。
~~~
if (window.Worker) {
// 支持
} else {
// 不支持
}
~~~
## 新建和启动子线程
主线程采用new命令,调用Worker构造函数,可以新建一个子线程。
~~~
var worker = new Worker('work.js');
~~~
Worker构造函数的参数是一个脚本文件,这个文件就是子线程所要完成的任务,上面代码中是work.js。由于子线程不能读取本地文件系统,所以这个脚本文件必须来自网络端。如果下载没有成功,比如出现404错误,这个子线程就会默默地失败。
子线程新建之后,并没有启动,必需等待主线程调用postMessage方法,即发出信号之后才会启动。postMessage方法的参数,就是主线程传给子线程的信号。它可以是一个字符串,也可以是一个对象。
~~~
worker.postMessage("Hello World");
worker.postMessage({method: 'echo', args: ['Work']});
~~~
## 子线程的事件监听
在子线程内,必须有一个回调函数,监听message事件。
~~~
/* File: work.js */
self.addEventListener('message', function(e) {
self.postMessage('You said: ' + e.data);
}, false);
~~~
self代表子线程自身,self.addEventListener表示对子线程的message事件指定回调函数(直接指定onmessage属性的值也可)。回调函数的参数是一个事件对象,它的data属性包含主线程发来的信号。self.postMessage则表示,子线程向主线程发送一个信号。
根据主线程发来的不同的信号值,子线程可以调用不同的方法。
~~~
/* File: work.js */
self.onmessage = function(event) {
var method = event.data.method;
var args = event.data.args;
var reply = doSomething(args);
self.postMessage({method: method, reply: reply});
};
~~~
## 主线程的事件监听
主线程也必须指定message事件的回调函数,监听子线程发来的信号。
~~~
/* File: main.js */
worker.addEventListener('message', function(e) {
console.log(e.data);
}, false);
~~~
## 错误处理
主线程可以监听子线程是否发生错误。如果发生错误,会触发主线程的error事件。
~~~
worker.onerror(function(event) {
console.log(event);
});
// or
worker.addEventListener('error', function(event) {
console.log(event);
});
~~~
## 关闭子线程
使用完毕之后,为了节省系统资源,我们必须在主线程调用terminate方法,手动关闭子线程。
~~~
worker.terminate();
~~~
也可以子线程内部关闭自身。
~~~
self.close();
~~~
## 主线程与子线程的数据通信
前面说过,主线程与子线程之间的通信内容,可以是文本,也可以是对象。需要注意的是,这种通信是拷贝关系,即是传值而不是传址,子线程对通信内容的修改,不会影响到主线程。事实上,浏览器内部的运行机制是,先将通信内容串行化,然后把串行化后的字符串发给子线程,后者再将它还原。
主线程与子线程之间也可以交换二进制数据,比如File、Blob、ArrayBuffer等对象,也可以在线程之间发送。
但是,用拷贝方式发送二进制数据,会造成性能问题。比如,主线程向子线程发送一个500MB文件,默认情况下浏览器会生成一个原文件的拷贝。为了解决这个问题,JavaScript允许主线程把二进制数据直接转移给子线程,但是一旦转移,主线程就无法再使用这些二进制数据了,这是为了防止出现多个线程同时修改数据的麻烦局面。这种转移数据的方法,叫做[Transferable Objects](http://www.w3.org/html/wg/drafts/html/master/infrastructure.html#transferable-objects)。
如果要使用该方法,postMessage方法的最后一个参数必须是一个数组,用来指定前面发送的哪些值可以被转移给子线程。
~~~
worker.postMessage(arrayBuffer, [arrayBuffer]);
window.postMessage(arrayBuffer, targetOrigin, [arrayBuffer]);
~~~
## 同页面的Web Worker
通常情况下,子线程载入的是一个单独的JavaScript文件,但是也可以载入与主线程在同一个网页的代码。假设网页代码如下:
~~~
<!DOCTYPE html>
<body>
<script id="worker" type="app/worker">
addEventListener('message', function() {
postMessage('Im reading Tech.pro');
}, false);
</script>
</body>
</html>
~~~
我们可以读取页面中的script,用worker来处理。
~~~
var blob = new Blob([document.querySelector('#worker').textContent]);
~~~
这里需要把代码当作二进制对象读取,所以使用Blob接口。然后,这个二进制对象转为URL,再通过这个URL创建worker。
~~~
var url = window.URL.createObjectURL(blob);
var worker = new Worker(url);
~~~
部署事件监听代码。
~~~
worker.addEventListener('message', function(e) {
console.log(e.data);
}, false);
~~~
最后,启动worker。
~~~
worker.postMessage('');
~~~
整个页面的代码如下:
~~~
<!DOCTYPE html>
<body>
<script id="worker" type="app/worker">
addEventListener('message', function() {
postMessage('Work done!');
}, false);
</script>
<script>
(function() {
var blob = new Blob([document.querySelector('#worker').textContent]);
var url = window.URL.createObjectURL(blob);
var worker = new Worker(url);
worker.addEventListener('message', function(e) {
console.log(e.data);
}, false);
worker.postMessage('');
})();
</script>
</body>
</html>
~~~
可以看到,主线程和子线程的代码都在同一个网页上面。
上面所讲的Web Worker都是专属于某个网页的,当该网页关闭,worker就自动结束。除此之外,还有一种共享式的Web Worker,允许多个浏览器窗口共享同一个worker,只有当所有网口关闭,它才会结束。这种共享式的Worker用SharedWorker对象来建立,因为适用场合不多,这里就省略了。
## 参考链接
* Matt West, [Using Web Workers to Speed-Up Your JavaScript Applications](http://blog.teamtreehouse.com/using-web-workers-to-speed-up-your-javascript-applications)
* Eric Bidelman, [The Basics of Web Workers](http://www.html5rocks.com/en/tutorials/workers/basics/)
* Eric Bidelman, [Transferable Objects: Lightning Fast!](http://updates.html5rocks.com/2011/12/Transferable-Objects-Lightning-Fast)
* Jesse Cravens, [Web Worker Patterns](http://tech.pro/tutorial/1487/web-worker-patterns)
* Bipin Joshi, [7 Things You Need To Know About Web Workers](http://www.developer.com/lang/jscript/7-things-you-need-to-know-about-web-workers.html)
- 第一章 导论
- 1.1 前言
- 1.2 为什么学习JavaScript?
- 1.3 JavaScript的历史
- 第二章 基本语法
- 2.1 语法概述
- 2.2 数值
- 2.3 字符串
- 2.4 对象
- 2.5 数组
- 2.6 函数
- 2.7 运算符
- 2.8 数据类型转换
- 2.9 错误处理机制
- 2.10 JavaScript 编程风格
- 第三章 标准库
- 3.1 Object对象
- 3.2 Array 对象
- 3.3 包装对象和Boolean对象
- 3.4 Number对象
- 3.5 String对象
- 3.6 Math对象
- 3.7 Date对象
- 3.8 RegExp对象
- 3.9 JSON对象
- 3.10 ArrayBuffer:类型化数组
- 第四章 面向对象编程
- 4.1 概述
- 4.2 封装
- 4.3 继承
- 4.4 模块化编程
- 第五章 DOM
- 5.1 Node节点
- 5.2 document节点
- 5.3 Element对象
- 5.4 Text节点和DocumentFragment节点
- 5.5 Event对象
- 5.6 CSS操作
- 5.7 Mutation Observer
- 第六章 浏览器对象
- 6.1 浏览器的JavaScript引擎
- 6.2 定时器
- 6.3 window对象
- 6.4 history对象
- 6.5 Ajax
- 6.6 同域限制和window.postMessage方法
- 6.7 Web Storage:浏览器端数据储存机制
- 6.8 IndexedDB:浏览器端数据库
- 6.9 Web Notifications API
- 6.10 Performance API
- 6.11 移动设备API
- 第七章 HTML网页的API
- 7.1 HTML网页元素
- 7.2 Canvas API
- 7.3 SVG 图像
- 7.4 表单
- 7.5 文件和二进制数据的操作
- 7.6 Web Worker
- 7.7 SSE:服务器发送事件
- 7.8 Page Visibility API
- 7.9 Fullscreen API:全屏操作
- 7.10 Web Speech
- 7.11 requestAnimationFrame
- 7.12 WebSocket
- 7.13 WebRTC
- 7.14 Web Components
- 第八章 开发工具
- 8.1 console对象
- 8.2 PhantomJS
- 8.3 Bower:客户端库管理工具
- 8.4 Grunt:任务自动管理工具
- 8.5 Gulp:任务自动管理工具
- 8.6 Browserify:浏览器加载Node.js模块
- 8.7 RequireJS和AMD规范
- 8.8 Source Map
- 8.9 JavaScript 程序测试
- 第九章 JavaScript高级语法
- 9.1 Promise对象
- 9.2 有限状态机
- 9.3 MVC框架与Backbone.js
- 9.4 严格模式
- 9.5 ECMAScript 6 介绍
- 附录
- 10.1 JavaScript API列表
- 草稿一:函数库
- 11.1 Underscore.js
- 11.2 Modernizr
- 11.3 Datejs
- 11.4 D3.js
- 11.5 设计模式
- 11.6 排序算法
- 草稿二:jQuery
- 12.1 jQuery概述
- 12.2 jQuery工具方法
- 12.3 jQuery插件开发
- 12.4 jQuery.Deferred对象
- 12.5 如何做到 jQuery-free?
- 草稿三:Node.js
- 13.1 Node.js 概述
- 13.2 CommonJS规范
- 13.3 package.json文件
- 13.4 npm模块管理器
- 13.5 fs 模块
- 13.6 Path模块
- 13.7 process对象
- 13.8 Buffer对象
- 13.9 Events模块
- 13.10 stream接口
- 13.11 Child Process模块
- 13.12 Http模块
- 13.13 assert 模块
- 13.14 Cluster模块
- 13.15 os模块
- 13.16 Net模块和DNS模块
- 13.17 Express框架
- 13.18 Koa 框架