雖然在JavaScript中有setInterval和setTimeout函數使javaScript看起來好像使多線程執行,單實際上JavaScript使單線程的,一次只能做一件事情(關於JavaScript單線程可以看看setTimeout()和setInterval() 何時被調用執行),看個簡單的例子證明一下
<!DOCTYPE html> <html> <head> <title>Web Workers</title> </head> <body> <h1>Web Workers</h1> <script type="text/javascript"> setTimeout(function(){ console.log('timeout function'); },1000); alert('do not close'); </script> </body> </html>
頁面一運行就會彈出一個對話框,如果setTimeout是在另外一個線程運行,那么過一秒鍾控制台就會打印“timeout function”,事實是只要不關閉對話框,控制台永遠不會輸出文字,這兩句話確實是在一個線程內運行的。
這樣的設計使JavaScript比較簡單,但有時候也很令人煩惱,因為單線程的設計意味着JavaScript代碼必須很快運行完,常見的問題就是一段復雜的JavaScript腳本會中斷頁面其它腳本執行,甚至會出現頁面失去響應,這也就是為什么ajax的API要設計成異步的。
Web Workers
在html5規范中引入了web workers概念,解決客戶端JavaScript無法多線程的問題,其定義的worker是指代碼的並行線程,不過web worker處於一個自包含的環境中,無法訪問主線程的window對象和document對象,和主線程通信只能通過異步消息傳遞機制。(《JavaScript權威指南》)
web worker
我們需要把希望單獨執行的javascript代碼放到一個單獨的js文件中,然后在頁面中調用Worker構造函數來創建一個線程,參數是該文件路徑,參數存放如果是相對地址,那么要以包含調用Worker構造函數語句所在腳本為參照,如果是絕對路徑,需要保證同源(協議+主機+端口)。這個文件不需要我們在頁面使用script標簽顯示引用
var worker=new Worker('js/worker.js');
這時候這個文件就會被異步加載並在后台執行,創建成功地worker是醬紫的
我們可以看到worker對象只有兩個屬性,其實是兩個回調函數句柄
- onerror:當worker運行出現錯誤,並且沒有在worker中ing捕獲,會在此捕獲
- onmessage:當worker向主線程發送消息是調用
在其prototype內有兩個重要方法
- postMessage:很熟悉的趕腳,之前我們介紹過window對象的postMessage()方法,woker的postMessage方法和window的比較類似,但參數略有不同,只需要傳遞消息內容就可以,而且支持所有JavaScript原生數據類型,當然不放心的話同樣也可以序列化為字符串傳遞
- terminate:終止worker執行,有些worker執行比較慢,主線程可以主動終止其執行
簡單的小例子
在一個頁面顯示0~10000內所有可以被n整除的數,當然我們不用i*n這種,要略微使計算顯得復雜一些嘛
index.html
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>Web Workers</title> 5 </head> 6 <body> 7 <h1>Web Workers</h1> 8 9 <div id="test" style="width:500px;"></div> 10 <script type="text/javascript"> 11 var worker=new Worker('js/worker.js'); 12 worker.postMessage({ 13 n:69 14 }); 15 16 worker.onmessage=function(e){ 17 var test=document.getElementById('test').innerHTML=e.data; 18 }; 19 </script> 20 </body> 21 </html>
/js/worker.js
function calc(n){ var result=[]; for(var i=1;i<10000;i++){ var tem=i; if(i%n==0){ if(i%(10*n)==0){ tem+='<br/>'; } result.push(tem); } } self.postMessage(result.join(' ')); self.close(); } onmessage=function(e){ calc(e.data.n); };
好像有幾點沒提到
worker.onmessage
綁定主線程的message事件,當worker調用postMessage時方法時,綁定的事件處理程序會被調用到,傳遞來的數據可以使用MouseEvent的data屬性獲取,通過target屬性還可以獲取worker對象
self是什么
self是woker中對自身的引用,有些像this
close()
在worker內部調用close()方法效果和在外部調用worker實例的terminate()方法效果一樣,終止worker運行
onmessage
在這個句柄內接收外部調用者傳遞的參數,參數可以通過e.data獲取
self.postMessage()
沒錯通過這個方法我們可以在worker內把結果傳遞給主線程,主線程上綁定的message事件的處理程序會被調用
worker的作用域
web worker的簡單用法就是這樣,但有些姿勢還是得了解一下。
全新的JavaScript環境
當一個Worker實例被創建的時候,它會在一個全新的JavaScript運行環境中,完全和創建worker的腳本分離開,即使我們傳遞的消息是引用類型它們也是復制傳遞的,修改worker中的參數不影響創建腳本中的參數。
importScripts()
我們可以通過importScripts()方法通過url在worker中加載庫函數,
importScripts('utility/dialog.js','common/cookie.js');
方法可以接受多個url,相對地址的url以當前worker為參照,方法會按照參數順序依次下載運行庫函數,如果中間某個腳本出錯,剩下的都不會被載入和執行,而且這個方法是同步的,只有所有腳本都加載、運行完后才會返回。
worker執行模型
worker線程從上到下同步運行它的代碼,然后進入異步階段來對事件及計時器響應,如果worker注冊了message事件處理程序,只要其有可能觸發,worker就一直在內存中,不會退出,但如果worker沒有監聽消息,那么當所有任務執行完畢(包括計數器)后,他就會退出。
web worker中可以使用什么
前面提到在worker中不能使用window對象和docuemnt對象,那么能夠使用什么呢
- JavaScript的全局對象:JSON、Date()、Array
- self自身引用
- location對象,但是其屬性都是只讀的,改了也影響不到調用者
- navigator對象
- setTimeout()、setInterval()及其對應清除方法
- addEventListener()、removeEventListener()
最后
web worker還支持sub worker和共享worker,這方面沒有太仔細看,瀏覽器兼容性也不討理想,有興趣同學可以上網搜索研究一下。