本文要介绍的是 HTML5 的 Web Workers 特性,它解决了 JavaScript 开发中一个重大的问题 —— 在后台运行 JavaScript 。与本系列前两篇文章介绍的特性相似,Web Wordkers 似乎也是为了 Web Apps 而设计的,可以想象,Web Apps 乃至原生 Apps 受移动设备性能的限制比在桌面环境中要大很多,尽管现在的移动设备性能已经不断的提高,这对于发展 Web Apps 的确是个很好的机遇,但是其性能表现始终受限,因此,对 Web Apps 开发来说 Web Workers 无疑是个十分实用的技术,使到 Web Apps 可以在移动设备上更好的运行。
一. 什么是 Web Workers ?
在介绍 Web Workers 的具体使用时,Kayo 首先正式说明一下 Web Workers 。当 HTML 页面中执行 JavaScript 时,都是单线程执行的,这时页面的状态是不可响应的,即页面的 UI 会被锁定,若执行需要的时间过长,页面甚至会出现假死状态。这样对于用户来说是个很不好的体验,他们只能等待 UI 恢复过来才能继续操作,对于开发者来说,这也很无奈。Web Workers 则使到 JavaScript 能在后台运行 ,即在后台创建相应的线程,把费时的处理交给后台,前台便能继续响应用户的操作,这样既不会影响页面的性能,又使到用户可以继续做想做的事。
可以看出,Web Workers 确实是个很棒的特性,但它仍有一些限制:
Web Workers 会完全独立于它们的执行页面,即在独立线程中工作,因此不能在 Web Workers 内访问 DOM 对象,window 对象,document 对象和 parent 对象。但 Web Workers 仍可以直接访问 navigator ,因此开发者可以访问 navigator 对象中如 appName , appVersion 和 userAgent 等一些属性。
由于这个限制的存在,我们无法直接利用 Web Workers 进行 DOM 操作,因此 Web Workers 更适合用于复杂的计算等耗费 CPU 的处理,这样可以尽量减轻 CPU 的负担,提升应用的性能。另外,需要开发者虽然无法直接在 Web Workers 内进行 DOM 操作,但可以在 Web Workers 的 message 事件的回调函数中进行 DOM 操作(关于 message 事件 Kayo 会在下面详细介绍),而把计算处理交给 Web Workers 在后台计算。
另外,也是由于 Web Workers 在独立线程中工作的原因,因此它也不能使用主线程中的变量和函数,也不能使用 alert 等会使到页面状态暂停的操作。
以上就是 Web Workers 的基本情况了,虽然仍有限制,但 Web Workers 已经有很实用的开发价值了。
二. 浏览器支持
Web Workers 在现代浏览器中都已经实现完整的支持,IE 则完全不支持。具体如下(以下版本号表明从该版本开始支持 Web Workers ,但并不一定完全支持 Web Workers 的完整功能,并且早期的具体表现在各浏览器之间有较大差异):
使用 Web Workers 运行 JavaScript 必须创建一个相应的 Web Workers 对象,例如,把需要执行的 JavaScript 都放在 demo_workers.js 中,而 demo_workers.js 与主页文档 index.html 处于同级目录,可以在 index.html 中使用以下语句创建相应的 Web Workers 对象:
var w = new Worker('demo_workers.js')
这样创建 Web Workers 对象之后,可以使用 “w” 引用这个 Web Workers 进行其他的操作了。
为了避免无效的操作,开发者还可以在创建及使用 Web Workers 之前判断浏览器是否支持 Web Wokers 。具体的代码如下:
if( typeof(Worker)!=="undefined" ){
// 创建 workers 对象
var w = new Worker('demo_workers.js');
} else {
alert('抱歉,你的浏览器不支持 Web Workers!');
}
2. 发送信息
在创建 Web Workers 对象后,就可以继续进一步的使用了,Web Workers 的常用操作包括发送信息和接受信息,之所以要使用专门的方法来发送和接收信息,是因为创建 Web Workers 对象后只会执行脚本,但无法直接与脚本互通信息,而有了信息的传递才是真正的利用 Web Workers,下面开始介绍这两个方法。
从前台发送信息到后台,通常是发送需要在 Web Workers 内部进行计算的原始数据,这需要在相应的 Web Workers 对象上调用 postMessage() 方法,例如发送一个字符串到上面创建的 “w” 的后台中,可以这样编写代码:
// 定义需要发送的信息字符串
var the_string = "This is a message.";
// 发送信息到后台
w.postMessage(the_string);
这样一个字符串便会发送到 Web Workers 后台了,而在 Web Workers 的后台,即上面的 “demo_workers.js” 内部,若需要发送信息(通常为 Web Workers 根据原始数据计算的结果数据),由于 Web Workers 对象已经默认是自身对象,因此无需再手动引用 Web Workers 对象。例如,我们需要发送一个结果字符串到前台,可以这样编写代码:
//发送信息到前台
postMessage(result);
3. 接收信息
既然有发送信息的方法,那么肯定也有接受信息的方法,接收信息的方法是监听 onmessage 事件,在 onmessage 事件的回调函数中有一个封装好的参数 event ,在 event 的 data 属性中包含了使用 postMessage() 发送过来的数据。这个接收方法也是可以在前台和后台中使用,与 postMessage() 相似,后台监听 onmessage 事件不需引用 Web Workers 对象。例如,针对上面发送信息的例子,接收相应的信息可以这样编写代码:
// 在后台中接收前台发送过来的信息
onmessage = function (event){
// 从 event 中获取数据,在这里其值为 "This is a message."
var original_data = event.data;
// 处理数据,得到结果
// 发送计算结果到前台
postMessage(result);
};
虽然 postMessage 中使用的数据类型并没有严格限制,但考虑到 postMessage 用于线程间的(即 Web Workers 中的线程与主页面的线程,或是多个有关联的 Web Workers 之间的线程)交互,因此建议使用 JavaScript 本地数据类型,如 Array , String , Date , Math ,同时也支持 JSON ,而不建议使用较为复杂的自定义类型。
3. location 对象的访问
Web Workers 可以访问 location 对象,但只可以以只读方式访问。开发者可以从 location 对象中获取 hostname 和 port 的值。
七. 完整实例
这里 Kayo 会使用例子进一步说明 Web Workers 的具体使用。Web Workers 的特长是进行复杂计算等耗费 CPU 的运算,而产生这种情况的通常原因要不就是算法费时,要不就是数据量大,或是两个原因同时都有。为了把代码集中于 Web Workers 的 API 调用部分,在例子中 Kayo 使用的方法是利用不复杂但效率低的递归算法配合大数据值的数据进行测试。
在下面的例子中,可以让用户输入两个整数,并计算它们的最大公约数和最小公倍数,计算方法采用递归算法,计算的部分写在独立的脚本文件中并交给 Web Workers 计算,建议用户输入数值较大的数据进行测试。当然,这个例子的主要意义只是演示 Web Workers 的使用方法,由于现在的桌面环境乃至移动设备环境都有了很大的提升,因此像例子中的算法即使使用大数值的数据进行测试也未必能体现 Web Workers 的优势,但在实际的 Web Apps 开发中,却很可能会出现比这个算法复杂得多的情况,例如从数据库中获取大量数据并运算,涉及物理模拟的运算,或是多个复杂算法需要并行运算,这样的情况都 很可能会需要较多的时间进行,导致页面 UI 出现一段时间不能响应用户操作,甚至在较低配置的移动设备上会发生崩溃,这时 Web Workers 便是很好的工具了。
// 获取用户输入然后发送到 workers 并在后台进行计算
function startWorker(){
if(typeof(Worker)!=="undefined"){
var first = document.getElementById('first').value;
var second = document.getElementById('second').value;
var nums = {'first': first, 'second': second};
// 创建 workers 对象
var w = new Worker('demo_workers.js');
w.postMessage(nums);
// 接受 workers 从后台返回的数据
w.onmessage = function (event){
document.getElementById('result').value = event.data;
};
} else {
alert('抱歉,你的浏览器不支持 Web Workers!');
}
};
demo_worker.js 代码
// 接收前台的数据并进行计算
onmessage = function (event){
var first = event.data.first;
var second = event.data.second;
var common_divisor = divisor(first, second);
var common_multiple = multiple(first, second);
// 发送计算结果到前台
postMessage("计算完毕! " + "最大公约数为 "+ common_divisor
+" 以及最小公倍数为 " + common_multiple);
};
// 计算最大公约数
function divisor(a, b) {
if (a % b == 0) {
return b;
} else {
return divisor(b, a % b);
}
};
// 计算最小公倍数
function multiple(a, b){
var multiple = 0;
multiple = a * b / divisor(a, b);
return multiple;
};