介紹
通過使用Web Worker, 我們可以在瀏覽器后台運行Javascript, 而不占用瀏覽器自身線程。Web Worker可以提高應用的總體性能,並且提升用戶體驗。如果你想在自己的Web應用中使用Web Worker, 不妨來了解一下有關Web Worker的7件事。
1. Web Worker 可以讓你在后台運行Javascript
一般來說Javascript和頁面的UI會共用一個線程,所以當點擊一個按鈕開始運行Javascript后,在這段代碼運行完畢之前,頁面是無法響應用戶操作的,換句話來說就是被“凍結”了。而這段代碼可以交給Web Worker在后台運行,那么頁面在Javascript運行期間依然可以響應用戶操作。后台會啟動一個worker線程來執行這段代碼,用戶可以創建多個worker線程。所以你可以在前台做一些小規模分布式計算之類的工作,不過Web Worker有以下一些使用限制:
- Web Worker無法訪問DOM節點;
- Web Worker無法訪問全局變量或是全局函數;
- Web Worker無法調用alert()或者confirm之類的函數;
- Web Worker無法訪問window、document之類的瀏覽器全局變量;
不過Web Worker中的Javascript依然可以使用setTimeout(),setInterval()之類的函數,也可以使用XMLHttpRequest對象來做Ajax通信。
2. 有兩種Web Worker
Web workers可分為兩種類型:專用線程dedicated web worker,以及共享線程shared web worker。 Dedicated web worker隨當前頁面的關閉而結束;這意味着Dedicated web worker只能被創建它的頁面訪問。與之相對應的Shared web worker可以被多個頁面訪問。在Javascript代碼中,“Work”類型代表Dedicated web worker,而“SharedWorker”類型代表Shared web worker。
在絕大多數情況下,使用Dedicated web worker就足夠了,因為一般來說在web worker中運行的代碼是專為當前頁面服務的。而在一些特定情況下,web worker可能運行的是更為普遍性的代碼,可以為多個頁面服務。在這種情況下,我們會創建一個共享線程的Shared web worker,它可以被與之相關聯的多個頁面訪問,只有當所有關聯的的頁面都關閉的時候,該Shared web worker才會結束。相對Dedicated web worker,shared web worker稍微復雜些。
3. “Worker”對象代表Dedicated Web Worker
現在來看如何使用Dedicated web worker。下面的例子中用到了jQuery以及Modernizr作為Javascript庫,然后往HTML頁面中加入以下代碼:
- <!DOCTYPE html>
- <html>
- <head>
- <title></title>
- <script type="text/javascript" src="script/modernizr.js"></script>
- <script type="text/javascript" src="script/jquery-2.0.0.js"></script>
- <script type="text/javascript">
- $(document).ready(function(){
- if(!Modernizr.webworker){
- alert("This browser doesn't support Web Worker!");
- return;
- }
- $("#btnStart").click(function(){
- var worker = new Worker("script/lengthytask.js");
- worker.addEventListener("message", function(evt){
- alert(evt.data);
- }, false);
- worker.postMessage(10000)
- });
- });
- </script>
- </head>
- <body>
- <form>
- <input type="button" id="btnStart" value="Start Processing"/>
- </form>
- </body>
- </html>
這個HTML頁面中有個按鈕,點擊后會運行一個Javascript文件。上面的代碼中首先檢測當前瀏覽器是否支持Web Worker,不支持的話,就跳出提醒信息。
按鈕的點擊事件中創建了Worker對象,並給它指定了Javascript腳本文件——lengthytask.js(稍后會有代碼),並且給Worker對象綁定了一個“message”事件。該事件會在后台代碼(lengthytask.js)向頁面返回數據時觸發。“message”事件可以通過event.data來獲取后台代碼傳回的數據。最后,postMessage方法正式執行lengthytask.js,該方法還可以向后台代碼傳遞參數, 后台代碼同樣通過message事件獲取該參數。
下面是lengthytask.js主要包含的代碼:
- addEventListener("message", function(evt){
- var date = new Date();
- var currentDate = null;
- do {
- currentDate = new Date();
- }while(currentDate - date < evt.data);
- postMessage(currentDate);
- }, false);
以上代碼在后台監聽message時間,並獲取頁面傳來的參數:10000;這里實際上是一個計時函數:在message事件被觸發10000毫秒之后,把結果(currentDate)傳給頁面。
所以當點擊“Start Processing”按鈕,頁面會在10秒鍾后把當時的時刻alert出來。在這10秒鍾內頁面依然可以響應鼠標鍵盤事件。
4. “SharedWorker”對象代表Shared Web Worker
前面的代碼使用的是dedicated web worker。 這一節會用shared web worker代替dedicated web worker,來區別兩者的不同。下面是同一個例子的shared web worker版本:
- addEventListener("message", function(evt){
- var date = new Date();
- var currentDate = null;
- do {
- currentDate = new Date();
- }while(currentDate - date < evt.data);
- postMessage(currentDate);
- }, false);
請注意加黑的代碼,這里創建了一個SharedWorker對象,並把message事件綁定在shared worker的port對象上;同樣由port對象發起postMessage, 開始執行后台代碼sharedlengthytask.js。
下面是sharedlengthytask.js的主要代碼:
- var port;
- addEventListener("connect", function(evt){
- port = evt.ports[0];
- port.addEventListener("message", function(evt){
- var date = new Date();
- var currentDate = null;
- do {
- currentDate = new Date();
- }while(currentDate - date < evt.data);
- port.postMessage(currentDate);
- }, false);
- port.start();
- }, false);
使用SharedWorker對象的后台代碼需要綁定connect和message事件, connect事件會在頁面上的port被start時觸發。之后的message事件的回調函數與之前的基本相同,最后port調用postMessage方法把結果傳回給頁面。
5. Web Worker使用XMLHttpRequest與服務端通信
有些情況下,web worker還需要與服務器進行交互。比如頁面可能需要處理來自數據庫中的信息,我們就需要使用Ajax技術與服務器交互,下面代碼包含了web worker如何從服務端獲取數據:
- addEventListener("message", function(evt){
- var xhr = new XMLHttpRequest();
- xhr.open("GET", "lengthytaskhandler.ashx");
- xhr.onload = function(){
- postMessage(xhr.responseText);
- };
- xhr.send();
- },false);
上面的代碼向服務端的asp.net服務lengthytaskhandler.ashx發出GET請求。並注冊了獲取數據后的onload事件。下面的代碼是服務端的lengthytaskhandler.ashx:
- namespace WebWorkerDemo
- {
- public class LengthyTaskHandler:IHttpHandler
- {
- public void ProcessRequest(HttpContext context)
- {
- System.Threading.Thread.Sleep(10000);
- context.Response.ContentType = "text/plain";
- content.Response.Write("Processing successful!");
- }
- public bool IsReusable
- {
- get
- {
- return false;
- }
- }
- }
- }
如你所見,ProcessRequest模擬了一個長時間運行的任務,並返回了“Processing successful!”的消息。
6. 通過Error事件捕捉錯誤信息
當我們把越來越復雜的邏輯加到Web Worker里時,錯誤處理機制是必不可少的。而Web Worker恰恰提供了error事件,供開發者捕捉錯誤信息。下面的代碼展示了如何綁定error事件:
- $("#btnStart").click(function(){
- var worker = new Worker("scripts/lengthytask.js");
- worker.addEventListener("error", function(evt){
- alert("Line #" + evt.lineno + " - " + evt.message + " in " + evt.filename);
- }, false);
- worker.postMessage(10000);
- });
如上可見, Worker對象可以綁定error事件;而且evt對象中包含錯誤所在的代碼文件(evt.filename)、錯誤所在的代碼行數(evt.lineno)、以及錯誤信息(evt.message)。
7. 通過terminate()方法終止Web Worker
有些情況下,我們可能需要強制終止執行中的Web Worker。Worker對象提供了terminate()來終止自身執行任務,被終止的Worker對象不能被重啟或重用,我們只能新建另一個Worker實例來執行新的任務。
總結
Web Worker可以在后台執行腳本,而不會阻塞頁面交互。Worker對象分為兩種:專用式Web Worker和共享式Web Worker:專用式的Web Worker只能被當個頁面使用,而共享式的Web Worker可以在被多個頁面使用。另外,本文還介紹了Web Worker的錯誤處理機制,以及使用Ajax與服務端交互。