浅谈Web Workers
一、简介
Web Workers是在HTML5中新增的,用来在Web应用程序中实现后台处理的一种技术。通过Web Workers你可以创建一个不会影响前台处理的后台线程,并且在这个后台线程中创建多个子线程。通过Web Workers,你可以将耗时较长的处理交给后台线程去运行,从而解决了HTML5之前因为某个处理耗时过长而跳出一个提示用户脚本运行时间过长,导致用户不得不结束这个处理的尴尬状况。
二、分类
Web workers可分为两种类型:专用线程dedicated web worker,以及共享线程shared web worker。 Dedicated web worker随当前页面的关闭而结束;这意味着Dedicated web worker只能被创建它的页面访问。与之相对应的Shared web worker可以被多个页面访问。在Javascript代码中,“Worker”类型代表Dedicated web worker,而“SharedWorker”类型代表Shared web worker。
三、Worker对象使用
3.1 常用API
1. 创建后台线程:
在Worker类的构造器中,将需要在后台线程中执行的脚本文件的URL地址作为参数,然后创建Worker对象就可以了。
var worker=new Worker("SumCalculate.js");
注意:在后台线程中是不能访问页面或窗口对象的。如果在后台线程的脚本文件中使用到window对象或者document对象,会引发错误。
2. 接收消息:
通过对Worker对象的onmessage事件句柄的获取可以在后台线程中接收消息。
worker.onmessage = function(event){ //处理收到的消息 };
3. 发送消息:
使用Worker对象的postMessage方法来对后台线程发送消息,发送的消息可以是文本数据,也可以是任何JavaScript对象(需要通过JSON对象的stringify方法将其转换成文本数据)。
worker.postMessage(message);
同样可以通过获取Worker对象的onmessage事件和postMessage方法在后台线程内部进行消息的接收和发送。
4. terminate()
主线程中终止worker,此后无法再利用其进行消息传递。注意:一旦terminate后,无法重新启用,只能另外创建。
worker.terminate();
5. error()
出错处理。且错误消息可以通过e.message来获取。
worker.onerror = function(e){ //打印出错消息 console.log(e.message); //中断与子线程的联系 worker.terminate(); }
四、 与线程进行数据的交互
1. 前端页面js代码
var intArray=new Array(100); var intStr=""; for (var i=0;i<100;i++){ intArray[i]=parseInt(Math.random()*100); if(i!=0){ intStr+=";"; } intStr+=intArray[i]; } var worker=new Worker("script.js"); worker.postMessage(intStr); worker.onmessage=function(event){ if(event.data!=""){ var j,k,tr,td,intArray=event.data.split(";"), table=document.getElementById("table"); for(var i=0;i<intArray.length;i++){ j=parseInt(i/10,0); k=i%10; if(k==0){ tr=document.createElement("tr"); tr.id="tr"+j; table.appendChild(tr); }else{ tr=document.getElementById("tr"+j); } td=document.createElement("td"); tr.appendChild(td); td.innerHTML=intArray[j*10+k]; td.style.backgroundColor="blue"; td.style.color="white"; td.width="30"; } } }
2. 后台线程代码,放在一个js文件中
onmessage=function(event){ var data=event.data; var returnStr=""; var intArray=data.split(";"); for(var i=0;i<intArray.length;i++){ if(parseInt(intArray[i])%3==0){ if(returnStr!=""){ returnStr+=";"; } returnStr+=intArray[i] } } postMessage(returnStr); }
在该示例中页面上随机生成了一个整数数组,然后将该正数数组传入线程,挑选出该数组中可以被3整除的数字,然后显示在页面的表格中。
五、线程嵌套
线程中可以嵌套子线程,这样的话我们可以把一个较大的后台线程切分成几个子线程,在每个子线程中各自完成相对独立的一部分工作。
示例:该示例中修改了前面所述与线程进行数据的交互中的示例,把生成随机数组的工作也放到后台线程中,然后使用一个子线程在随机数组中挑选可以被3整除的数字。代码如下:
1. 前端页面js代码
var worker=new Worker("script.js"); worker.postMessage(" "); worker.onmessage=function(event){ if(event.data!=""){ var j,k,tr,td,intArray=event.data.split(";"), table=document.getElementById("table"); for(var i=0;i<intArray.length;i++){ j=parseInt(i/10,0); k=i%10; if(k==0){ tr=document.createElement("tr"); tr.id="tr"+j; table.appendChild(tr); }else{ tr=document.getElementById("tr"+j); } td=document.createElement("td"); tr.appendChild(td); td.innerHTML=intArray[j*10+k]; td.style.backgroundColor="blue"; td.style.color="white"; td.width="30"; } } }
2. 后台线程代码,放在一个js文件中
onmessage=function(event){ var intArray=new Array(100); for (var i=0;i<100;i++){ intArray[i]=parseInt(Math.random()*100); } var worker=new Worker("worker2.js"); worker.postMessage(JSON.stringify(intArray)); worker.onmessage=function(event){ postMessage(event.data); } }
在线程中,向子线程提交消息时使用子线程对象的postMessage方法,而向本线程的创建源发送消息时直接使用postMessage方法。
3.子线程代码,放在一个js文件中
onmessage = function(event){ var intArray = JSON.parse(event.data); var returnStr; returnStr=""; for(var i=0;i<intArray.length;i++){ if(parseInt(intArray[i])%3 == 0){ if(returnStr!=""){ returnStr+=";" } returnStr+=intArray[i]; } } postMessage(returnStr); close(); }
注意:在子线程中向发送源发送回消息后,如果该子线程不再使用,最好使用close语句关闭子线程。
六、在多个子线程中进行数据的交互
要实现子线程与子线程之间的交互,大致需要如下几个步骤:
1)先创建发送数据的子线程。
2)执行子线程中任务,然后把要传递的数据发送给主线程。
3)在主线程接收到子线程传回来的消息时,创建接收数据的子线程,然后把发送数据的子线程中返回的消息传递给接收数据的子线程。
4)执行接收数据子线程中的代码。
1. 主线程代码
onmessage=function(event){ var worker=new Worker("worker1.js"); worker.postMessage(""); worker.onmessage=function(event){ var data = event.data; worker = new Worker("worker2.js"); worker.postMessage(data); worker.onmessage=function(event){ var data = event.data; postMessage(data); } } }
2. 发送数据子线程代码
onmessage=function(event){ var intArray=new Array(100); for(var i=0;i<100;i++){ intArray[i]=parseInt(Math.random()*100); } postMessage(JSON.stringify(intArray)); close(); }
3. 接收数据子线程代码
onmessage=function(event){ var intArray = JSON.parse(event.data); var returnStr; returnStr=""; for(var i=0;i<intArray.length;i++){ if(parseInt(intArray[i])%3 == 0){ if(returnStr!=""){ returnStr+=";" } returnStr+=intArray[i]; } } postMessage(returnStr); close(); }
七、线程中可用的变量、函数与类
- self:用来表示本线程范围内的作用域
- postMessage:用于向创建线程的源窗口发送消息
- onmessage:获取接收消息的事件
- importScripts(urls):导入其他JavaScript脚本文件。参数为该脚本文件的url地址,可以导入多个脚本文件。导入的脚本文件必须与使用该线程文件的页面在同一个域中,且在同一个端口中。
- navigator对象:与window.navigator对象类似,具有appName,platform,userAgent,appVersion属性。
- sessionStorage/localStorage:可以在线程中使用Web Storage
- XMLHttpRequest:可以在线程中处理Ajax请求
- Web Workers:可以在线程中嵌套线程
- setTimeout/setInterval:可以在线程中实现定时处理
- close:用于结束本线程
- eval,isNaN,escape等可以使用所有JavaScript核心函数
- object:可以创建和使用本地对象
- Websockets:可以使用WebSockets API来向服务器发送和接收消息
- FileSystem:可以在线程中通过同步FileSystem API来实现受沙箱保护的文件系统中的文件及目录的创建、更新及删除操作
八、SharedWorker使用
1. 创建SharedWorker对象
var worker = new SharedWorker(url,[name]);
其中第一个参数指定后台脚本文件的URL地址,第二个参数为可选参数,用于指定Worker的名称。
var worker = new SharedWorker('test.js');
2. 当SharedWorker对象被创建时,一个MessagePort对象也同时被创建,可以通过SharedWorker对象的port属性来访问该对象,该对象表示页面通信时需要使用的窗口,具有如下所示的三个方法:
postMessage方法:用于向另一个页面发送消息
start方法:用于激活端口,开始监听端口是否接收到消息
close方法:用于关闭并停用窗口
每个MessagePort对象都具有一个message事件,当端口接收到消息时触发该事件。
可以通过监听MessagePort对象的message事件并指定事件处理函数的方法来指定在该端口接收到消息时所做的处理。
port.onmessage=function(event){ //处理收到的消息 }
也可以通过MessagePort对象的addEventListener方法来监听message事件的触发,但在这种情况下必须使用MessagePort对象的start方法显式的激活端口,开始消息的监听
port.addEventListener('message', function(event){ //处理收到的消息 },false); port.start();
3. 当某个页面通过SharedWorker对象与共享后台线程开始通信时,会触发后台线程对象的connect事件,可以监听该事件并且在后台脚本文件中定义该事件触发时所做的处理
onconnect=function(event){ //定义事件处理函数 }
在事件处理函数中,event参数值(代表被触发的事件对象)的port属性值为一个集合,其中第一个数组即为该页面中的SharedWorker对象的port属性值,即代表该页面用于发送或者获取消息的端口的MessagePort对象。
onconnect=function(e){ var port=e.ports[0]; port.onmessage=function(e){ port.postMessage(e.data*e.data); } }
参考书籍:《HTML5与CSS3权威指南》
样例代码可以去我的github下载:https://github.com/sakuramoon0203/Web-Worker