自JavaScript诞生以来,还没有办法在浏览器UI线程之外运行代码。网页工人线程API改变了这种状况,它引入一个接口,使代码运行而不占用浏览器UI线程的时间。作为最初的HTML 5的一部分,网页工人线程API已经分离出去成为独立的规范。网页工人线程已经被Firefox3.5,Chrome 3,和Safari 4原生实现。
网页工人线程对网页应用来说是一个潜在的巨大性能提升,因为新的工人线程在自己的线程中运行JavaScript。这意味着,工人线程中的代码运行不仅不会影响浏览器UI,而且也不会影响其它工人线程中运行的代码。
(一)工人线程运行环境
由于网页工人线程不绑定UI线程,这也意味着它们将不能访问许多浏览器资源。JavaScript和UI更新共享同一个进程的部分原因是它们之间互访频繁,如果这些任务失控将导致糟糕的用户体验。网页工人线程修改DOM 将导致用户界面出错,但每个网页工人线程都有自己的全局运行环境,只有JavaScript特性的一个子集可用。工人线程的运行环境由下列部分组成:
(1)一个浏览器对象,只包含四个属性:appName, appVersion, userAgent, 和platform
(2)一个location 对象(和window 里的一样,只是所有属性都是只读的)
(3)一个self对象指向全局工人线程对象
(4)一个importScripts()方法,使工人线程可以加载外部JavaScript文件
(5)所有ECMAScript 对象,诸如Object,Array,Data,等等。
(6)XMLHttpRequest 构造器
(7)setTimeout()和setInterval()方法
(8)close()方法可立即停止工人线程
因为网页工人线程有不同的全局运行环境,你不能在JavaScript代码中创建。事实上,你需要创建一个完全独立的JavaScript文件,包含那些在工人线程中运行的代码。要创建网页工人线程,你必须传入这个JavaScript文件的URL:
var worker = new Worker("code.js");
此代码一旦执行,将为指定文件创建一个新线程和一个新的工人线程运行环境。此文件被异步下载,直到下载并运行完之后才启动工人线程。
(二)工人线程交互
工人线程和网页代码通过事件接口进行交互。网页代码可通过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 的实例,其它类型就不允许了。有效数据被序列化,传入或传出工人线程,然后反序列化。即使看上去对象直接传了过去,实例其实是同一个数据完全独立的表述。试图传递一个不支持的数据类型将导致JavaScript错误。
加载外部文件
当工人线程通过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 解析将在后面第七章讨论)。假设数据足够大,至少需要500毫秒才能完成解析任务。很显然时间太长了以至于不能允许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毫秒或更多时间,也没有必要添加更多代码来分解处理过程。此处理过程发生在一个独立的线程中,所以你可以让它一直运行完解析过程而不会干扰用户体验。页面使用postMessage()将一个JSON 字符串传给工人线程。工人线程在它的onmessage 事件句柄中收到这个字符串也就是event.data,然后开始解析它。完成时所产生的JSON 对象通过工人线程的postMessage()
方法传回页面。然后此对象便成为页面onmessage 事件句柄的event.data。请记住,此工程只能在Firefox 3.5和更高版本中运行,而Safari 4 和Chrome 3 中,页面和工人线程之间只允许传递字符串。
解析一个大字符串只是许多受益于网页工人线程的任务之一。其它可能受益的任务如下:
(1)编/解码一个大字符串
(2)复杂数学运算(包括图像或视频处理)
(3)给一个大数组排序
任何超过100毫秒的处理,都应当考虑工人线程方案是不是比基于定时器的方案更合适。当然,还要基于浏览器是否支持工人线程。