HTML5 Web Worker
首先本文只讨论 window.Worker
而不包含SharedWorker.毕竟这玩意连firefox4 beta7 都还没有支持.(貌似chrome safari opera 的较新版本都已经支持.)
有一点要提前说明的是:worker线程中的代码具有独立的执行环境. 我们可以把它假想成一个mini的global环境. 有兴趣的同学可以去看相关文档.
在这个执行环境中.不要在奢望你能调用绝大多数宿主提供给我们的对象、属性、方法.你大概可以认为,我们只能用的上js内置对象和方法.其他的一律不考虑.
不过 在该执行上下文内.我们可以使用这样一些东东:
self : 即当前worker 的GlobalWorker对象. 这上面有些特殊的宿主对象.比如 location、navigator、setTimeout、setInterval之类的.
一个好消息是我们可以调用 JSON 对象. 剩下的基本就是我们下面要介绍的一些方法喽.
worker有同源策略限制. 即worker无法引入一个跨域的资源来直接使用.(但是worker的一个方法可以绕过同源策略限制,来动态加载其他域脚本)
浏览器支持:
IE: 无 (IE10已开始支持.)
FireFox: 3.5+
Safair: 4+
Chrome 3+
Opera : 10+
主要接口: (详细API可自行查看)
接口定义相关链接 : http://www.whatwg.org/specs/web-workers/current-work/#the-workerglobalscope-abstract-interface
var worker = new Worker('a.js');
outside :
事件:
1.onmessage 事件 :当a.js 即 worker线程中调用 postMessage(sData)方法时, onmessage可以侦听该事件,并执行指定回调方法
worker.onmessage = function(e){e.data// 我们需要的sData}
2.onerror 事件 : 捕获a.js中worker线程中语法异常和运行时各类异常.(注:这玩意也管 404那种加载失败滴异常.除了某些浏览器...)
worker.onerror = function(e){e.message // 错误信息e.lineno // 行号e.filename //返回完整的错误文件的url .};注:worker.addEventListener('message',function(e){...});// 这样也中.
方法:
1. postMessage 方法 : 向 worker 发送消息. worker线程 必须同时注册onmessage事件.
2. terminate 方法 : 停掉worker的行为.
inside :
事件:
1.onmessage 事件 :当out页调用 worker.postMessage(sData)方法时, onmessage可以侦听该事件,并执行指定回调方法
方法:
1. postMessage 方法 : 向 out页发送消息. out页必须同时注册worker.onmessage事件.
2. close 方法 : 停掉worker的行为.
3. importScripts : 方法,worker线程动态加载外部脚本用. 该方法会冻结worker线程.直到动态加载脚本加载完毕或执行完毕(浏览器差异)
.importScripts 方法支持同事加载多个脚本:importScripts('a.js','b.js','c.js');
.同事加载多个脚本在各个浏览器中均为并行加载.(前提是HTTP连接数够用).
.但执行顺序是严格按照参数顺序进行的.即 先执行a 后执行b 然后才是c. 无论哪个先加载完.(FF有差异)
遗憾的是 safari4 在最初支持了 worker的同时,并没有提供原生 JSON对象的支持. 如果我们的项目不考虑支持safari4,那么真是个好消息.否则,可能为了让worker更健壮的工作,我们需要自己写一个JSON转换了.
更加遗憾的是.对于web worker 的一些 行为,浏览器实现,也有比较大的差异.
浏览器差异:
关于异常:
opera中, 一旦发生一个语法错误或运行时错误.有时候会多抛出一个 Internal error 给你. 并且lineno 总是0.
opera中, 一般的语法错误或执行期异常(如调用未定义变量,throw new Error(s)除外) 总是无法给出准确的错误信息.且lineno 总是0.
chrome 和 safari 中, worker.onerror 并不捕获加载失败的404异常.chrome 作为 worker 的先驱.值得鄙视一下.
关于worker引入文件的缓存问题:
opera中,一但.js被缓存那么,即使右键-重新载入 ,也会直接去cache读. 你不用指望会有什么304 或200.这个真没有.
我们有2个选择,要么完全放弃客户端缓存, 要么就更换版本号...好纠结的问题.
调用close()方法后的差异
opera中,一但在worker线程中调用close()方法.那么就代表着一切都结束了.如果在close(); 后你仍然试图使用worker中的某些属性、事件、或方法则会抛出一个Internal error . 但其他浏览器则不会.
opera中,调用close()方法后, 出现的postMessage('')给主页面传递信息,不会再触发主页面的onmessage回调. 其他浏览器则可以.
FF 中, 调用close()后 worker 虽然不再响应页面的postMessage .但是在close();调用之前.页面postMessage给worker线程的信息.则仍然会在.worker线程结束后被触发回调. 其他浏览器则不会.
chrome中 ,调用close()后. self对象被赋值为null . 但你仍然可以和其他浏览器一样 直接调用 WorkerGlobalScope对象的方法.所以,应避免使用self.postMessage, self.onmessage=function(){} ,self.close();而采用直接调用的方式.(如onmessage=function(){};)
关于worker.terminate()方法后的差异
opera中,主页面调用此方法后. 再调用worker.postMessage(sData)会抛出异常. 其他浏览器则不会.(虽然此时,worker.postMessage仍然是一个方法)
opera 中worker.terminate() 带来的问题:
1. worker.onerror 触发回调时要在主页面并不是在使用worker的脚本退出所在执行环境后才会被触发.而其他浏览器则如此. 考虑下面的代码:
var worker = new Worker("error.js");//抛出一个异常.worker.onerror = function(e){alert(e);//opera则会触发. 其他浏览器则不会.};var t = new Date;while(new Date - t < 3000);worker.terminate();//问题就出在 这里...
2. worker.onmessage 也存在同样的问题. 考虑下面的代码 :
var worker = new Worker("b.js"); //b.js中 第一时间postMessage(sData);worker.onmessage=function(e){alert(e.data);//只有opera 会触发b.js中的这次回调. 并打印出e.data}var t = new Date;while(new Date - t < 3000);worker.terminate();
关于importScripts方法:
FF中,importScripts方法加载脚本时. 虽然同样会冻结当前worker线程.但执行期是在worker线程结束后..即,其他浏览器则不是如此..其他浏览器的 加载 执行,都会冻结住worker的线程.
FF中,importScripts 方法加载多个脚本时,执行顺序不确定.应是先到先执行.
FF safari chrome 中,importScripts方法加载脚本失败时,会抛出异常,且可被out页面的 worker.onerror 捕获,opera浏览器则不会.
FF中,importScripts方法加载多个脚本如a.js,b.js,c.js,虽然是并行加载.但执行却要等待a b c三个文件都加载完毕后才开始执行. 期间 任何一个文件404错误.都将导致直接退出当前执行环境并且a.js b.js c.js 以及worker自身代码都将得不到执行. 其他浏览器则不是这样.
safari chrome,使用importScripts同时加载多个脚本时,任何一个404错误,都将导致退出当前执行环境. 但先加载好的外部脚本可以被正常执行.
opera 则无视404错误.当它不存在.继续干后面的活.
关于worker内创建worker的差异:
safari chrome中,不允许worker内部再次创建一个worker. FF 和 opera 则可以. (即safari chrome WorkerGlobal不具备一个名为Worker的成员)
总结:
.对于worker对象没有onload事件.我们不必疑惑.因为你在worker加载的.js没有加载好之前的,所有postMessage 都会在worker 加载并且执行完毕后,被触发回调.所以我们完全不必担心这个问题.
.对于跨浏览器的worker.我们遵守 逻辑不依赖执行顺序,可以很好的避免一些,看起来很诧异的问题.
.worker 内部可以继续创建另外一个worker.用于多线程并行运算于交互.对于多个worker及页面间的共享通讯.请参考SharedWorker 对象.目前浏览器支持还有待完善. 但是 worer内部的worker仍然不可以跨域.即worker引用资源的url 必须是相对路径(即使引入当前worker的脚本是一个位于其他域的.被importScripts引入到外层worker中的脚本.相对路径也永远是相对 outside 页面的路径).或同域的绝对路径.
.对于IE 不支持这东西. 暂时没什么好办法,无非是用 setTimeout 之类的去模拟多线程. 毕竟我们用worker 的最终目的,在不影响UI update 及 浏览器与用户交互的情况下, 可以做大规模运算.