1、概述
JavaScript語言采用的是單線程模型,也就是說,所有任務(wù)排成一個(gè)隊(duì)列,一次只能做一件事。隨著電腦計(jì)算能力的增強(qiáng),這一點(diǎn)帶來很大的不便,無法充分發(fā)揮JavaScript的潛能。龍其考慮到,F(xiàn)ile API允許JavaScript讀取本地文件,就更加如此了。
Web Worker的目就,就是為JavaScript創(chuàng)造多線程環(huán)境,允許主線程將一些任務(wù)分配給子線程。在主線程運(yùn)行的同時(shí),子線程在后臺(tái)運(yùn)行,兩者互不干擾。等到子線程完成計(jì)算任務(wù),再把結(jié)果返回給主線程。因此,每一個(gè)子線程就好像一個(gè)“工人”(worker),默默地完成自己的工作。
Web Worker有以下幾個(gè)特點(diǎn):
-
同域限制:子線程加載的腳本文件,必須與主線程的腳本文件在同一個(gè)域。
-
DOM限制:子線程無法讀限網(wǎng)頁的DOM對(duì)象,即document、window、parent這些對(duì)象,子線程都無法得到。(但是,navigator對(duì)象和location對(duì)象可以獲得。)
-
腳本限制:子線程無法讀取網(wǎng)頁的全局變量和函數(shù),也不能執(zhí)行alert和confirm方法,不過可以執(zhí)行setInterval和setTimeout,以及使用XMLHttpRequest對(duì)象發(fā)出AJAX請(qǐng)求。
-
文件限制:子線程無法讀取本地文件,即子線程無法打開本機(jī)的文件系統(tǒng)(file://),它所加載的腳本,必須來自網(wǎng)絡(luò)。
使用之前,檢查瀏覽器是否支持這個(gè)API。支持的瀏覽器包括 IE 10+、Firefox 3.6+、Safari 4.0+、Chrome 和 Opera 11,但是手機(jī)瀏覽器還不支持。
if (window.Worker) { // 支持} else { // 不支持}
2、新建和啟動(dòng)子線程
在主線程內(nèi)部,采用new命令調(diào)用Worker方法,可以新建一個(gè)子線程。
var worker = new Worker('work.js');
Worker方法的參數(shù)是一個(gè)腳本文件,這個(gè)文件就是子線程所要完成的任務(wù),上面代碼中是work.js。由于子線程不能讀取本地文件系統(tǒng),所以這個(gè)腳本文件必須來自網(wǎng)絡(luò)端。如果下載沒有成功,比如出現(xiàn)404錯(cuò)誤,這個(gè)子線程就會(huì)默默地失敗。
子線程新建之后,并沒有啟動(dòng),必需等待主線程調(diào)用postMessage方法,即發(fā)出信號(hào)之后才會(huì)啟動(dòng)。
worker.postMessage('hello world');
postMessage方法的參數(shù),就是主線程傳給子線程的信號(hào)。它即可以是一個(gè)字符串,也可以是一個(gè)對(duì)象。
worker.postMessage({method: 'each', args: ['work']});
3、子線程的事件監(jiān)聽
在子線程內(nèi),必須有一個(gè)回調(diào)函數(shù),監(jiān)聽message事件。
//File: work.jsself.addEventListener('message', function(e) { self.postMessage('You said: ' + e.data); }, false);
self代表子線程自身,self.addEventListener表示對(duì)子線程的message事件指定回調(diào)函數(shù)(直接指定onmessage屬性的值也可以)?;卣{(diào)函數(shù)的參數(shù)是一個(gè)事件對(duì)象,它的data屬性包含主線程發(fā)來的信號(hào)。self.postMessage則表示,子線程向主線程發(fā)送一個(gè)信號(hào)。
根據(jù)主線程發(fā)來的不同的信號(hào)值,子線程可以調(diào)用不同的方法。
'message', method = args = reply =);
4、主線程的事件監(jiān)聽
主線程也必須指定message事件的回調(diào)函數(shù),監(jiān)聽子線程發(fā)來的信號(hào)。
// File: main.jsworker.addEventListener('message', function(e) { console.log(e.data); }, false);
5、錯(cuò)誤處理
主線程可以監(jiān)聽子線程是否發(fā)生錯(cuò)誤。如果發(fā)生錯(cuò)誤,會(huì)觸發(fā)主線程的error事件。
worker.onerror(function(e) { console.log(e); });// orworker.addEventListener('error', function(e) { console.log(e); }, false);
6、關(guān)閉子線程
使用完畢后,為了節(jié)省系統(tǒng)資源,我們必須在主線程調(diào)用terminate方法,手動(dòng)關(guān)閉子線程。
worker.terminate();
也可以子線程內(nèi)部關(guān)閉自身。
self.close();
7、主線程與子線程的數(shù)據(jù)通信
主線程與子線程之間的通信內(nèi)容,可以是文本,也可以是對(duì)象。需要注意的是,這種通信是拷貝關(guān)系,即是傳值而不是傳址,子線程對(duì)通信內(nèi)容的修改,不會(huì)影響到主線程。事實(shí)上,瀏覽器內(nèi)部的運(yùn)行機(jī)制是,先將通信內(nèi)容串行化,然后把串行化后的字符串發(fā)給子線程,后者再將它還原。
主線程也子線程這間也可以交換二進(jìn)制數(shù)據(jù),比如File、Blob、ArrayBuffer等對(duì)象,也可以在線程之間發(fā)送。
但是,用拷貝方式發(fā)送二進(jìn)制數(shù)據(jù),會(huì)造成性能問題。比如,主線程向子線程發(fā)送一個(gè)500MB文件,默認(rèn)情況下瀏覽器會(huì)生成一個(gè)原文件的拷貝。為了解決這個(gè)問題,JavaScript允許主線程把二進(jìn)制數(shù)據(jù)直接轉(zhuǎn)移給子線程,但是一旦轉(zhuǎn)移,主線程就無法再使用這些二進(jìn)制數(shù)據(jù)了,這是為了防止出現(xiàn)多個(gè)線程同時(shí)修改數(shù)據(jù)的麻煩局面。這種轉(zhuǎn)移數(shù)據(jù)的方法,叫做Transferable Objects。
如果要使用該方法,postMessage方法的最后一個(gè)參數(shù)必須是一個(gè)數(shù)組,用來指定前面發(fā)送的哪些值可以被轉(zhuǎn)移給子線程。
worker.postMessage(arrayBuffer, [arrayBuffer]);
8、同頁面的Web Worker
通常情況下,子線程載入的是一個(gè)單獨(dú)的JavaScript文件,但是也可以載入與主線程在同一個(gè)網(wǎng)頁的代碼。假設(shè)網(wǎng)頁代碼如下:
<!DOCTYPE html> <body> <script id="worker" type="app/worker"> addEventListener('message', function() { postMessage('Im reading Tech.pro'); }, false); </script> </body></html>
我們可以讀取頁面的script,用worker來處理。
var blob = new Blob([document.querySelector('#workere').textContent]);
這里需要把代碼當(dāng)作二進(jìn)制數(shù)據(jù)讀取,所以使用Blob接口。然后,這個(gè)二進(jìn)制對(duì)象轉(zhuǎn)為URL,再通過這個(gè)URL創(chuàng)建worker。
var url = window.URL.createObjectURL(blob);var worker = new Worker(url);
部署事件監(jiān)聽代碼。
worker.addEventListener('message', function(e) { console.log(e.data); }, false);
最后啟動(dòng)worker。
worker.postMessage('');
整個(gè)頁面的代碼如下:
<!DOCTYPE html> <body> <script id="worker" type="app/worker"> addEventListener('message', function() { postMessage('Work done!'); }, false); </script> <script> (function() { var blob = new Blob([document.querySelector('#worker').textContent]); var url = window.URL.createObjectURL(blob); var worker = new Worker(url); worker.addEventListener('message', function(e) { console.log(e.data); }, false); worker.postMessage(''); })(); </script> </body></html>
可以看到,主線程和子線程的代碼都在同一個(gè)網(wǎng)頁上面。
上面所講的Web Worker都是專屬于某個(gè)網(wǎng)頁的,當(dāng)該網(wǎng)頁關(guān)閉,worker就自動(dòng)結(jié)束。除此之外,還有一種共享式的Web Worker,允許多個(gè)瀏覽器窗口共享同一個(gè)worker,只有當(dāng)所有窗口關(guān)閉,它才會(huì)結(jié)束。這種共享式的Worker用SharedWorker對(duì)象來創(chuàng)建,因?yàn)檫m用場(chǎng)合不多,這里就省略了。