自JavaScript诞生以来,还没有办法在浏览器UI线程之外运行代码。网页工人线程API改变了这种状况,它引入一个接口,使代码运行而不占用浏览器UI线程的时间。作为最初的HTML 5的一部分,网页工人线程API已经分离出去成为独立的规范(http://www.w3.org/TR/workers/)。网页工人线程已经被Firefox 3.5、Chrome 3和Safari 4原生实现。
网页工人线程对网页应用来说是一个潜在的巨大性能提升,因为新的工人线程在自己的线程中运行JavaScript。这意味着,工人线程中运行的代码不仅不会影响浏览器UI线程,而且也不会影响其他工人线程中运行的代码。
由于网页工人线程不绑定浏览器UI线程,这也意味着它们将不能访问许多浏览器资源。JavaScript和UI更新共享同一个进程的部分原因是它们之间互访频繁,如果互访失控将导致糟糕的用户体验。网页工人线程修改DOM将导致用户界面出错,因为每个网页工人线程都有自己的全局运行环境,只有JavaScript特性的一个子集可用。工人线程的运行环境由下列部分组成:
❑一个浏览器对象,只包含4个属性:appName、appVersion、userAgent和platform。
❑一个Location对象(和Window对象的一样,只是所有属性都是只读的)。
❑一个Self对象指向全局工人线程对象。
❑一个importScripts方法,使工人线程可以加载外部JavaScript文件。
❑所有ECMAScript对象,如Object、Array、Data等。
❑XMLHttpRequest构造器。
❑setTimeout和setInterval方法。
❑close方法可立即停止工人线程。
因为网页工人线程有不同的全局运行环境,所以不能在JavaScript代码中创建网页工人线程。事实上,需要创建一个完全独立的JavaScript文件,以包含那些在工人线程中运行的代码。要创建网页工人线程,必须传入这个JavaScript文件的URL:
var worker=new Worker(/"code.js/");
此代码一旦执行,将为指定文件创建一个新线程和一个新的工人线程运行环境。此JavaScript文件被异步下载,直到下载并运行完此文件之后才启动工人线程。
工人线程和网页代码通过事件接口进行交互。网页代码可通过postMessage方法向工人线程传递数据,它接收单个参数,即传递给工人线程的数据。此外,在工人线程中还有onmessage事件句柄用于接收信息。例如:
var worker=new Worker(/"code.js/");
worker.onmessage=function(event){
alert(event.data);
};
worker.postMessage(/"Nicholas/");
网页工人线程从message事件中接收数据。这里定义了一个onmessage事件句柄,事件对象具有一个data属性用于存放传入的数据。网页工人线程可通过它自己的postMessage方法将信息返回给页面。
self.onmessage=function(event){
self.postMessage(/"Hello,/"+event.data+/"!/");
};
最终的字符串结束于网页工人线程的onmessage事件句柄。消息系统是页面和网页工人线程之间唯一的交互途径。只有某些类型的数据可以使用postMessage传递,这些数据可以是原始值(string、number、boolean、null和undefined),也可以是Object和Array的实例,其他类型的数据就不允许传递了。有效数据被序列化,然后传入或传出工人线程,最后反序列化。当工人线程通过importScripts方法加载外部JavaScript文件时,它接收一个或多个URL参数来指出要加载的JavaScript文件网址。工人线程以阻塞方式调用importScripts,直到所有文件加载完成并执行之后,脚本才继续运行。由于网页工人线程在UI线程之外运行,因此这种阻塞不会影响UI响应。例如:
importScripts(/"file1.js/",/"file2.js/");
self.onmessage=function(event){
self.postMessage(/"Hello,/"+event.data+/"!/");
};
此代码第一行包含两个JavaScript文件,它们将在网页工人线程中使用。
网页工人线程适合于那些纯数据的或与浏览器UI没关系的长运行脚本。这种线程看起来用处不大,不过在网页应用程序中通常有一些数据处理功能将受益于网页工人线程,而不是定时器。
例如,解析一个很大的JSON字符串(JSON解析将在第7章讨论)。假设数据足够大,至少需要500 ms才能完成解析任务。很显然,时间太长会导致不允许JavaScript在客户端上运行网页工人线程,因为它会干扰用户体验。由于此任务难以分解成用于定时器的小段任务,所以工人线程成为理想的解决方案。下面的代码说明了网页工人线程在网页上的应用。
var worker=new Worker(/"jsonparser.js/");
worker.onmessage=function(event){
var jsonData=event.data;
evaluateData(jsonData);
};
worker.postMessage(jsonText);
工人线程的代码负责JSON解析,例如:
self.onmessage=function(event){
var jsonText=event.data;
var jsonData=JSON.parse(jsonText);
self.postMessage(jsonData);
};
注意,即使JSON.parse可能需要500 ms或更多时间,也没有必要添加更多代码来分解处理过程。由于此处理过程发生在一个独立的线程中,因此可以让它一直运行完解析过程而不会干扰用户体验。
页面使用postMessage将一个JSON字符串传给工人线程。工人线程在它的onmessage事件句柄中收到这个字符串也就是event.data,然后开始解析它。完成解析时所产生的JSON对象通过工人线程的postMessage方法传回页面,此后此对象便成为页面onmessage事件句柄的event.data。记住,此工程只能在Firefox 3.5及其更高版本中运行,而在Safari 4和Chrome 3中,页面和网页工人线程之间只允许传递字符串。解析一个大字符串只是许多受益于网页工人线程的任务之一。其他可能受益的任务如下:
❑编/解码一个大字符串。
❑复杂数学运算(包括图像或视频处理)。
❑给一个大数组排序。
在进行任何超过100 ms的处理时,都应当考虑工人线程方案是不是比基于定时器的方案更合适,当然,还要考虑浏览器是否支持工人线程。