Web Workers 提供了一个简单的方法:在后台线程中运行网页中的脚本。worker 一旦创建后,就可以发消息给正在运行的任务,该任务通过创建者 post 消息到相应的事件句柄所指定。(没看懂直接看调用图)
worker 线程能够在用户界面进行渲染的时候执行任务。此外,他们能够使用 XMLHttpRequest 进行 I/O 操作。
知识扩展(引自:http://fed.renren.com/2010/01/247#more-247)
一个浏览器至少存在三个线程:js引擎线程(处理js)、GUI渲染线程(渲染页面)、浏览器事件触发线程(控制交互)。
1:JavaScript引擎是基于事件驱动单线程执行的,JS引擎一直等待着任务队列中任务的到来然后加以处理,浏览器无论再什么时候都只有一个JS线程在运行JS程序。
2:GUI 渲染线程负责渲染浏览器界面,当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。但需要注意 GUI渲染线程与JS引擎是互斥的,当JS引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。
3:事件触发线程,当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理。这些事件可来自JavaScript引擎当前执行的 代码块如setTimeOut、也可来自浏览器内核的其他线程如鼠标点击、AJAX异步请求等,但由于JS的单线程关系所有这些事件都得排队等待JS引擎 处理。
了解了浏览器的内核处理方式就不难理解浏览器为什么会进入假死状态了,当一段JS脚本长时间占用着处理机就会挂起浏览器的GUI更新,而后面的事件响应 也被排在队列中得不到处理,从而造成了浏览器被锁定进入假死状态。另外JS脚本中进行了DOM操作,一旦JS调用结束就会马上进行一次GUI渲染,然后才 开始执行下一个任务,所以JS中大量的DOM操作也会导致事件响应缓慢甚至真正卡死浏览器,如在IE6下一次插入大量的HTML。而如果真的弹出了“脚本 运行时间过长“的提示框则说明你的JS脚本肯定有死循环或者进行过深的递归操作了。
现在如果遇到了这种情况,我们可以做的不仅仅是优化代码,html5的webWorkers提供了js的后台处理线程的API,它允许将复杂耗时的单纯 js逻辑处理放在浏览器后台线程中进行处理,让js线程不阻塞UI线程的渲染。这个线程不能和页面进行交互,如获取元素、alert等。多个线程间也是可 以通过相同的方法进行数据传递。
线程安全
Worker 可以真正达到 OS 级别的线程,如果你过于粗糙的话,你代码中的并发将会导致一些“有趣”的现象。然而,就 web workers 来说,只要控制好节点和其它线程之间的通信是很难引起并发问题的。这里不需要你通过具体的数据和线程中通过序列化的对象去访问非线程安全的组件或 DOM。因此,在你的代码中很难会因此引发问题。
创建 Worker
创建一个 worker 很容易。你只需要调用 Worker() 这个构造器,传入指定脚本的 URI 去执行 worker 线程。如果你希望接受到 worker 的通知,可设置 worker 的 onmessager 属性为一个事件句柄的 function。如下:
var worker = new Worker(“../js/demos.js”);
worker.onmessage = function(event) {
console.log(“Called back by the worker!\n”);
};
或者
var worker = new Worker(“../js/demos.js”);
worker.addEventListener(‘message’, function(event) {
console.log(“Called back by the worker!\n”);
}, false);
worker.postMessage(); // start the worker. </pre>
数据传输
消息可以通过主线程中的 postMessage() 进行发送。 worker.onmessage 的 event 参数中包含的 data 属性就是传输的数据,其严格遵循 json.
worker.onmessage = function (event){
// event.data = demos.js 中的 postMessage 参数
console.log(event.data);
}
worker.postMessage(30000);
demos.js
onmessage = function (event) {
// event.data = 3000;
for (var i = 0; i < event.data; i++) {
if (0 === i % 10 ) {
postMessage(i);
}
};
};
Terminating a worker
如果你需要立即终止一个正在运行的 worker,你可以调用 terminate() 方法。
worker 线程立即被终止将没有机会完成她的操作或者清除她自己。
worker 通过调用 close() 方法可以关闭自身。
Handling errors
当在 worker 运行时发生错误时,将会调用 onerror 事件句柄。她接受一个名为 error 的 event 来实现 ErrorEcent 接口。event 并不会冒泡,它可以取消;为阻止默认的事件被替换, worker 可以调用 error event 的 preventDefault() 方法。
error 中的 event 参数包含很多信息,其中以下三个较为重要:
message
- 人类可读的错误信息。
filename
- 发生错误的文件名。
lineno
- 脚本发生错误的行号。
导入脚本库
worker 线程允许一个全局的函数 importScript(),可以导入脚本或者库到她们的域中。她接受0个或多个引入资源的 URI。
importScripts(); / imports nothing /
importScripts(‘foo.js’); / imports just “foo.js” /
importScripts(‘foo.js’, ‘bar.js’); / imports two scripts /
浏览器会加载和执行她们。worker 将会使用这些来自脚本中的任何的全局对象。
脚本下载无序,但会按照传入到 importScripts() 中的文件名顺序进行执行。这时同步的;
importScripts()
直到所有的脚本被加载和执行后才会返回。
Demo
html
<!DOCTYPE html>
<html>
<head>
<meta charset=”UTF-8”>
<title>web workers</title>
<script src=”http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.js"></script>
</head>
<body>
<a href=”#” id=”startWorker”>show</a>
<ul id=”worker-list”></ul>
<script type=”text/javascript”>
$(function(){
function createWorker (msgObj) {
var worker = new Worker(“../js/demos.js”);
worker.onmessage = function (event){
msgObj.html(event.data);
}
worker.onerror = function(event){
alert(“错误行号:”+ event.lineno +”/n错误信息:” + event.message);
}
return worker;
}
$("#startWorker").click(function(){
for(var i=0;i<5;i++){
addworker();
}
$(this).remove();
return false;
});
function addworker(){
var newItem = $('<li />');
var msg = $('<span class="message">Waitting...</span>');
var close = $('<span class="close">X</span>').click(function(){
var li = $(this).closest('li');
li.data('worker').terminate();
li.remove();
});;
var worker = createWorker (msg);
newItem.data('worker',worker);
worker.postMessage(30000);
$('#worker-list').append(newItem.append(msg).append(close))
}
});
</script>
</body>
</html>
../demos.js
onmessage = function (event) {
for (var i = 0; i < event.data; i++) {
if (0 === i % 10 ) {
postMessage(i);
}
}
};