讀古今文學網 > 編寫高質量代碼:改善JavaScript程序的188個建議 > 建議122:推薦網頁工人線程 >

建議122:推薦網頁工人線程

自JavaScript誕生以來,還沒有辦法在瀏覽器UI線程之外運行代碼。網頁工人線程API改變了這種狀況,它引入一個接口,使代碼運行而不佔用瀏覽器UI線程的時間。作為最初的HTML 5的一部分,網頁工人線程API已經分離出去成為獨立的規範(http://www.w3.org/TR/workers/)。網頁工人線程已經被Firefox 3.5、Chrome 3和Safari 4原生實現。

網頁工人線程對網頁應用來說是一個潛在的巨大性能提升,因為新的工人線程在自己的線程中運行JavaScript。這意味著,工人線程中運行的代碼不僅不會影響瀏覽器UI線程,而且也不會影響其他工人線程中運行的代碼。

由於網頁工人線程不綁定瀏覽器UI線程,這也意味著它們將不能訪問許多瀏覽器資源。JavaScript和UI更新共享同一個進程的部分原因是它們之間互訪頻繁,如果互訪失控將導致糟糕的用戶體驗。網頁工人線程修改DOM將導致用戶界面出錯,因為每個網頁工人線程都有自己的全局運行環境,只有JavaScript特性的一個子集可用。工人線程的運行環境由下列部分組成:

❑一個瀏覽器對象,只包含4個屬性:appName、appVersion、userAgent和platform。

❑一個Location對像(和Window對象的一樣,只是所有屬性都是只讀的)。

❑一個Self對像指向全局工人線程對象。

❑一個importScripts方法,使工人線程可以加載外部JavaScript文件。

❑所有ECMAScript對象,如Object、Array、Data等。

❑XMLHttpRequest構造器。

❑setTimeout和setInterval方法。

❑close方法可立即停止工人線程。

因為網頁工人線程有不同的全局運行環境,所以不能在JavaScript代碼中創建網頁工人線程。事實上,需要創建一個完全獨立的JavaScript文件,以包含那些在工人線程中運行的代碼。要創建網頁工人線程,必須傳入這個JavaScript文件的URL:


var worker=new Worker(\"code.js\");


此代碼一旦執行,將為指定文件創建一個新線程和一個新的工人線程運行環境。此JavaScript文件被異步下載,直到下載並運行完此文件之後才啟動工人線程。

工人線程和網頁代碼通過事件接口進行交互。網頁代碼可通過postMessage方法向工人線程傳遞數據,它接收單個參數,即傳遞給工人線程的數據。此外,在工人線程中還有onmessage事件句柄用於接收信息。例如:


var worker=new Worker(\"code.js\");

worker.onmessage=function(event){

alert(event.data);

};

worker.postMessage(\"Nicholas\");


網頁工人線程從message事件中接收數據。這裡定義了一個onmessage事件句柄,事件對像具有一個data屬性用於存放傳入的數據。網頁工人線程可通過它自己的postMessage方法將信息返回給頁面。


self.onmessage=function(event){

self.postMessage(\"Hello,\"+event.data+\"!\");

};


最終的字符串結束於網頁工人線程的onmessage事件句柄。消息系統是頁面和網頁工人線程之間唯一的交互途徑。只有某些類型的數據可以使用postMessage傳遞,這些數據可以是原始值(string、number、boolean、null和undefined),也可以是Object和Array的實例,其他類型的數據就不允許傳遞了。有效數據被序列化,然後傳入或傳出工人線程,最後反序列化。當工人線程通過importScripts方法加載外部JavaScript文件時,它接收一個或多個URL參數來指出要加載的JavaScript文件網址。工人線程以阻塞方式調用importScripts,直到所有文件加載完成並執行之後,腳本才繼續運行。由於網頁工人線程在UI線程之外運行,因此這種阻塞不會影響UI響應。例如:


importScripts(\"file1.js\",\"file2.js\");

self.onmessage=function(event){

self.postMessage(\"Hello,\"+event.data+\"!\");

};


此代碼第一行包含兩個JavaScript文件,它們將在網頁工人線程中使用。

網頁工人線程適合於那些純數據的或與瀏覽器UI沒關係的長運行腳本。這種線程看起來用處不大,不過在網頁應用程序中通常有一些數據處理功能將受益於網頁工人線程,而不是定時器。

例如,解析一個很大的JSON字符串(JSON解析將在第7章討論)。假設數據足夠大,至少需要500 ms才能完成解析任務。很顯然,時間太長會導致不允許JavaScript在客戶端上運行網頁工人線程,因為它會干擾用戶體驗。由於此任務難以分解成用於定時器的小段任務,所以工人線程成為理想的解決方案。下面的代碼說明了網頁工人線程在網頁上的應用。


var worker=new Worker(\"jsonparser.js\");

worker.onmessage=function(event){

var jsonData=event.data;

evaluateData(jsonData);

};

worker.postMessage(jsonText);


工人線程的代碼負責JSON解析,例如:


self.onmessage=function(event){

var jsonText=event.data;

var jsonData=JSON.parse(jsonText);

self.postMessage(jsonData);

};


注意,即使JSON.parse可能需要500 ms或更多時間,也沒有必要添加更多代碼來分解處理過程。由於此處理過程發生在一個獨立的線程中,因此可以讓它一直運行完解析過程而不會干擾用戶體驗。

頁面使用postMessage將一個JSON字符串傳給工人線程。工人線程在它的onmessage事件句柄中收到這個字符串也就是event.data,然後開始解析它。完成解析時所產生的JSON對像通過工人線程的postMessage方法傳回頁面,此後此對像便成為頁面onmessage事件句柄的event.data。記住,此工程只能在Firefox 3.5及其更高版本中運行,而在Safari 4和Chrome 3中,頁面和網頁工人線程之間只允許傳遞字符串。解析一個大字符串只是許多受益於網頁工人線程的任務之一。其他可能受益的任務如下:

❑編/解碼一個大字符串。

❑複雜數學運算(包括圖像或視頻處理)。

❑給一個大數組排序。

在進行任何超過100 ms的處理時,都應當考慮工人線程方案是不是比基於定時器的方案更合適,當然,還要考慮瀏覽器是否支持工人線程。