WebWorker类似于浏览器中的多线程操作。之前的JS中,无论你是使用setTimeout、setInterval 还是 使用了XMLHttpRequest,都是在一个线程里面。前两个使用消息队列,XMLHttpRequest则是浏览器会帮你进行闲时进行。归根结底,都是在一个线程里面跑。如果用户想进行一些阻塞操作,很可能会产生卡住页面的情况。甚至于我们想实现一个类似于Android的专用、公用Service,那该怎么办?
1.Web Worker是什么
Web Worker 是HTML5标准的一部分,这一规范定义了一套 API,它允许一段JavaScript程序运行在主线程之外的另外一个线程中。Web Worker 规范中定义了两类工作线程,分别是专用线程Dedicated Worker和共享线程 Shared Worker,其中,Dedicated Worker只能为一个页面所使用,而Shared Worker则可以被多个页面所共享,本文示例为专用线程Dedicated Worker。
chrome浏览器是多进程架构,分为Browser进程以及Render进程。每打开一个页面,浏览器都会为其分配一个Render进程,webkit以及js都运行在这个进程内。如果要起一个DedicatedWorker,Chrome会在Render进程中起一个线程。如果要起一个SharedWorker就稍微复杂一点,必须起一个专门的进程,并且,相同的SharedWorker不管你创建多少次,都只存在一个。
2.使用Dedicated Worker的主页面代码main.js
var worker = new Worker("task.js");
worker.postMessage(
{
id:1,
msg:'Hello World'
}
);
worker.onmessage=function(message){
var data = message.data;
console.log(JSON.stringify(data));
worker.terminate();
};
worker.onerror=function(error){
console.log(error.filename,error.lineno,error.message);
}
Dedicated Worker所执行的代码task.js
onmessage = function(message){
var data=message.data;
data.msg = 'Hi from task.js';
postMessage(data);
}
在main.js代码中,首先通过调用构造函数,传入了worker脚本文件名,新建了一个worker对象,在我的理解中,这一对象是新创建的工作线程在主线程的引用。随后调用worker.postMessage()方法,与新创建的工作线程通信,这里传入了一个json对象。随后分别定义了worker对象的onmessage事件和onerror事件的回调处理函数,当woker线程返回数据时,onmessage回调函数执行,数据封装在message参数的data属性中,调用 worker 的 terminate()方法可以终止worker线程的运行;当worker线程执行出错时,onerror回调函数执行,error参数中封装了错误对象的文件名、出错行号和具体错误信息。
在task.js代码中,定义了onmessage事件处理函数,由主线程传入的数据,封装在message对象的data属性中,数据处理完成后,通过postMessage方法完成与主线程通信。在工作线程代码中,onmessage事件和postMessage方法在其全局作用域可以访问。
3.worker线程执行流程
通过查阅资料,webKit加载并执行worker线程的流程如下图所示
- worker线程的创建的是异步的
代码执行到”var worker = new Worker(task.js’)“时,在内核中构造WebCore::JSWorker对象(JSBbindings层)以及对应的WebCore::Worker对象(WebCore模块),根据初始化的url地址”task.js”发起异步加载的流程;主线程代码不会阻塞在这里等待worker线程去加载、执行指定的脚本文件,而是会立即向下继续执行后面代码。
- postMessage消息交互由内核调度
main.js中,在创建woker线程后,立即调用了postMessage方法传递了数据,在worker线程还没创建完成时,main.js中发出的消息,会先存储在一个临时消息队列中,当异步创建worker线程完成,临时消息队列中的消息数据复制到woker对应的WorkerRunLoop的消息队列中,worker线程开始处理消息。在经过一轮消息来回后,继续通信时, 这个时候因为worker线程已经创建,所以消息会直接添加到WorkerRunLoop的消息队列中;
4.worker线程数据通讯方式
主线程与子线程数据通信方式有多种,通信内容,可以是文本,也可以是对象。需要注意的是,这种通信是拷贝关系,即是传值而不是地址,子线程对通信内容的修改,不会影响到主线程。事实上,浏览器内部的运行机制是,先将通信内容串行化,然后把串行化后的字符串发给子线程,后者再将它还原。
主线程与子线程之间也可以交换二进制数据,比如File、Blob、ArrayBuffer等对象,也可以在线程之间发送。但是,用拷贝方式发送二进制数据,会造成性能问题。比如,主线程向子线程发送一个50MB文件,默认情况下浏览器会生成一个原文件的拷贝。为了解决这个问题,JavaScript允许主线程把二进制数据直接转移给子线程,转移后主线程无法再使用这些数据,这是为了防止出现多个线程同时修改数据的问题,这种转移数据的方法,叫做Transferable Objects。
// Create a 32MB "file" and fill it.
var uInt8Array = new Uint8Array(1024*1024*32); // 32MB
for (var i = 0; i < uInt8Array .length; ++i) {
uInt8Array[i] = i;
}
worker.postMessage(uInt8Array.buffer, [uInt8Array.buffer]);
5.API进阶
在worker线程中,可以获得下列对象
navigator对象
location对象,只读
XMLHttpRequest对象
setTimeout/setInterval方法
Application Cache
通过importScripts()方法加载其他脚本
创建新的Web Worker
worker线程不能获得下列对象
DOM对象
window对象
document对象
parent对象
上述的规范,限制了在worker线程中获得主线程页面相关对象的能力,所以在worker线程中,不能进行dom元素的更新。