Web Worker 是什么

Web Worker 为 JavaScript 在浏览器里的运行提供了多线程接口。这样一来,我们就可以把那些复杂的耗时的不涉及DOM操作的运算抛给浏览器的一个后台线程来处理。(据说)这种线程是操作系统级别的。

让我们来看一段代码,简单了解一下 Web Worker 的 API:

/*
	@constructor 构造函数 Worker(JSURL);
	@param {DOMString} JSURL是要在后台线程中执行的JS文件的URL,注:传入 Worker 构造函数的参数 URL 必须遵循同源策略
	@return {Worker} Worker对象实例
*/
var worker = new Worker('run_in_the_background_thread.js'); 
/*
	@property {function} onmessage 后台线程发回消息事件的监听函数
	@param {Event} event.data中存放着后台线程发送回来的处理结果
*/
worker.onmessage = function(event){
	console.log(event); 
};
/*
	@property {function} onerror 错误事件监听函数
	@param {Event} event.message 错误信息
	               event.filename 发生错误的脚本文件名
	               event.lineno 发生错误的具体行号
*/
worker.onerror = function(event){
	console.log(event); 
};
/*
	@method postMessage() 向后台线程发送消息
	@param {any} 传递给后台线程的数据
	@param {Array} 用于转交对象所有权的一个数组对象
*/
worker.postMessage({x: 200, y: 300});
/*
	@method terminate() 终止后台线程
*/
worker.terminate();

 

以上的API是在主线程中用的,那在后台线程的代码中除了可以用 onmessage 属性 和 postMessage方法(它们的用法与主线程中相同)之外还有一些常用的专属API:

/*
	@property {function} onmessage 主线程发送消息事件的监听函数
	@param {Event} event.data中存放着后主程发送的数据
*/
self.onmessage = function(event){
	console.log(event);
}
/*
	@method postMessage() 向后主程发送消息
	@param {any} 传递给主线程的数据
	@param {Array} 用于转交对象所有权的一个数组对象
*/
self.postMessage({newX:400, newY:600});
/*
	@method terminate() 终止线程自身
*/
self.close();
/*
	@method importScripts() 该函数允许线程将脚本或库引入自己的作用域内
	@param {DOMString} 要引入的JS文件URL,可以是多个
*/
self.importScripts('a.js', 'b.js');
/*	最后来说说self
	@porperty {WorkerGlobalScope} self 线程全局作用域的一个引用对象
	//以上代码在线程中全局作用域下可以不加self.而直接调用(关于作用域就是另一个话题了)
*/

 

Web Worker 怎么工作

通过上面API的简单介绍,你应该大致明白HTML5多线程是怎么工作的了。我写了一个简单的例子:主线程发送用户输入数据给后台线程,后台线程进行二次方运算后再发送回主线程。下面是主要的代码:

worker.html 

<!doctype html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Worker</title>
	<script type="text/javascript" src="jquery-1.8.3.min.js"></script>
	<script type="text/javascript" src="worker_main.js"></script>
</head>
<body>
	<input id='input'type="text" value=0>
	<input id='send' type="button" value='send'>
	<input id='output' type="text" value='result'>
</body>
</html>

worker_main.js 

$(function(){
	var worker_bg = new Worker('worker_bg.js');
	worker_bg.onmessage = function(event){
		var result = event.data;
		$('#output').val(result.output);
	};
	worker_bg.onerror = function(event){
		console.log(event);
	};
	$('#send').on('click',function(event){
		worker_bg.postMessage({'input':$('#input').val()});
	});
});

worker_bg.js 

self.onmessage = function(event){
	var param = event.data;
	var result = {};
	result.output = Math.pow(parseInt(param.input),2);
	self.postMessage(result);
};

以上就是HTML5多线程的一个简单应用。实际效果可以看这里(Demo)。

有几点要注意一下:

1)Worker构造函数中传入的JS文件的URL,要遵守同源策略策略。所谓同源策略,反过来说就是不能跨域,简单的说就是域名相同,端口相同,协议相同。详见JavaScript的同源策略

2)在后台线程中你不能再使用console.log()对代码进行调试输出了,我测试目前只有IE10支持在后台线程中使用console.log(),庆幸的是有解决方案,详见WorkerConsole.js 

3)线程中不能操作: DOM(非线程安全)、window 对象、dowcument 对象、parent 对象;线程中可以操作:navigator对象、location对象(只读)、XMLHttpRequest(Ajax)、setTimeout(),clearTimeout()和setInterval(),clearInterval()、Application Cache(应用缓存)、生成其他Web Worker。

 

Web Worker 应用之 Ajax

想想怎么在多线程中使用Ajax,我们已经习惯于jQuery的便捷,但是jQuery库是Dom相关的( jQuery is a JavaScript DOM library.),所以在后台线程中基本没戏,不过也有人把jQuery中Dom无关的那部分代码分离出来,专供Web Worker使用。详见……(呃,我真的忘了那个Github地址,有空再找找吧)

不过,假设我们现在准备实现一个自己的库,这个库专用于后台线程,Dom无关,我们给它起个名字叫Workto.js(听上去是不是大有成为流行库的可能?),我们可以通过$.ajax({……});来使用它的Ajax功能,下面是它的实现代码:

Workto.js

var Workto = (function(){
	var $ = {};
	$.ajax = function(options){
		var url = options.url || '',
			data = JSON.stringify(options.data) || '',
			type = options.type || 'POST',
			success = options.success || function(msg){},
			error = options.error || function(err){},
			async = options.async || true,
			xhr = new XMLHttpRequest();
		xhr.open(type, url, false);
		xhr.setRequestHeader('Content-Type', 'application/json;  charset=utf-8');
		xhr.onreadystatechange = function () {
			if (xhr.readyState == 4 /*&& xhr.status == 200*/) {
				success(xhr.responseText);
			}else{
				error({status:xhr.status,readyState:xhr.readyState});
			}
		}
		xhr.send(data);
	};
	return $;
})();
self.$ = self.Workto = Workto;

要用到importScripts方法在后台进程中加载我们的库:

worker_behide.js 

self.importScripts('Workto.js');
this.onmessage = function(event){
	var param = event.data;
	var result = {};
	$.ajax({
		url:'workto.js',
		success: function(response){
			result.output = response;
			self.postMessage(result);
		}
	});
};

我们假设用ajax请求了Workto.js文件,然后把文件的文本内容返回给主线程:

worker_main.js 

$(function(){
	var worker_behide = new Worker('worker_behide.js');
	worker_behide.onmessage = function(event){
		var result = event.data;
		$('#output').val(result.output);
	};
	worker_behide.onerror = function(event){
		console.log(event);
	};
	$('#send').on('click',function(event){
		worker_behide.postMessage();
	});
});

写个简单的html来验证一下执行结果:

worker.html 

<input id='send' type="button" value='load'><br/>
<textarea id='output'></textarea>

实际的效果可以看这里(Demo)。

扩展阅读

1)使用 Web Worker https://developer.mozilla.org/zh-CN/docs/DOM/Using_web_workers

2) Web Worker API https://developer.mozilla.org/zh-CN/docs/DOM/Worker

3) Web Worker 的基本信息 http://www.html5rocks.com/zh/tutorials/workers/basics/