這一次咱們將分拆Web Workers:咱們將提供一個概述,討論不一樣類型的workers,他們的組成部分如何共同發揮做用,以及他們在不一樣狀況下提供的優點和侷限性。最後,咱們將提供5個用例,其中Web Workers將是不錯的選擇。html
您應該已經熟悉JavaScript在單個線程上運行的事實,由於咱們以前已經詳細討論過它。可是,JavaScript也爲開發人員提供了編寫異步代碼的機會。ajax
咱們以前已經討論過異步編程,什麼時候應該使用它。算法
異步編程經過在事件循環中「調度」要稍後執行的部分代碼來使應用程序UI可以響應,從而容許首先執行UI渲染。編程
異步編程的一個很好的用例是製做AJAX請求。 因爲請求可能須要很長時間,所以能夠異步製做請求,而且在客戶端等待響應時,能夠執行其餘代碼。api
// This is assuming that you're using jQuery jQuery.ajax({ url: 'https://api.example.com/endpoint', success: function(response) { // Code to be executed when a response arrives. } });
可是,這會帶來一個問題 - 請求由瀏覽器的WEB API處理,但其餘代碼如何能夠異步? 例如,若是成功回調中的代碼是CPU密集型的:數組
var result = performCPUIntensiveCalculation();
若是performCPUIntensiveCalculation不是一個HTTP請求,而是一個阻塞代碼(例如一個巨大的for循環),則沒法釋放事件循環並解除瀏覽器的用戶界面 - 它會凍結並對用戶無響應。瀏覽器
這意味着異步函數僅解決JavaScript語言的單線程侷限性的一小部分。緩存
在某些狀況下,經過使用setTimeout,您能夠在從更長時間運行的計算中解除UI的狀況下取得良好結果。例如,經過在獨立的setTimeout調用中對複雜的計算進行批處理,您能夠將它們置於事件循環中的單獨「位置」,這樣就能夠得到UI渲染/響應性所需的時間。安全
咱們來看看一個計算數值數組平均值的簡單函數:服務器
function average(numbers) { var len = numbers.length, sum = 0, i; if (len === 0) { return 0; } for (i = 0; i < len; i++) { sum += numbers[i]; } return sum / len; }
這就是你如何重寫上面的代碼並「模擬」異步性:
function averageAsync(numbers, callback) { var len = numbers.length, sum = 0; if (len === 0) { return 0; } function calculateSumAsync(i) { if (i < len) { // Put the next function call on the event loop. setTimeout(function() { sum += numbers[i]; calculateSumAsync(i + 1); }, 0); } else { // The end of the array is reached so we're invoking the callback. callback(sum / len); } } calculateSumAsync(0); }
這將使用setTimeout函數,該函數將在事件循環的更下方添加計算的每一個步驟。 在每次計算之間,將有足夠的時間進行其餘計算,這是解凍瀏覽器所必需的。
HTML5給咱們帶來了不少很棒的東西,包括:
Web Workers是瀏覽器內的線程,可用於執行JavaScript代碼而不會阻止事件循環。
這真是太神奇了。 JavaScript的整個範例基於單線程環境的思想,但在這裏來自網絡工做者(Web Workers),它解除了(部分)這種限制。
Web Workers容許開發人員將長時間運行和計算密集型任務放在後臺,而不會阻止用戶界面,從而使您的應用程序更具響應能力。更重要的是,爲了解決事件循環的問題,不須要使用setTimeout技巧。
下面是一個簡單的演示,顯示了在有和沒有Web Workers的狀況下對數組進行排序的區別。
Web Workers容許您執行諸如啓動長時間運行的腳原本處理計算密集型任務,但不會阻止UI。實際上,這一切都是平行進行的。Web Workers真正是多線程的。
你可能會說 - 「JavaScript不是單線程語言嗎?」。
當你意識到JavaScript是一種沒有定義線程模型的語言時,這應該是你的'aha!'時刻。 Web Workers不是JavaScript的一部分,它們是能夠經過JavaScript訪問的瀏覽器功能。大多數瀏覽器從來都是單線程的(固然,這已經改變了),而且大多數JavaScript實現都發生在瀏覽器中。 Web Worker沒有在Node.JS中實現 - 它有一個「cluster」或「child_process」的概念,有點不一樣。
值得注意的是,這個規範提到了三種類型的Web Workers:
專用Web Worker由主進程實例化,而且只能與其進行通訊。
共享工做人員能夠經過運行在同一來源的全部進程(不一樣的瀏覽器選項卡,iframe或其餘共享工做人員)訪問。
服務工做人員是一個事件驅動的工做人員,針對原點和路徑進行了註冊。 它能夠控制與之關聯的網頁/網站,攔截並修改導航和資源請求,並以很是細化的方式緩存資源,從而使您能夠很好地控制應用在某些狀況下的行爲方式(例如,當網絡不是可用。)
在這篇文章中,咱們將關注Dedicated Workers(專職工做者)並將他們稱爲「(Web Workders)網絡工做者」或「(Workers)工做者」。
Web Workers被實現爲.js文件,這些文件經過頁面中的異步HTTP請求提供。 Web Worker API徹底隱藏了這些請求。
工做人員利用相似線程的消息傳遞來實現並行性。 它們很是適合保持您的用戶界面的最新性,性能和響應能力。
Web工做人員在瀏覽器中的獨立線程中運行。 所以,它們執行的代碼須要包含在單獨的文件中。 記住這一點很是重要。
讓咱們看看如何建立一個基本的工做人員:
var worker = new Worker('task.js');
若是「task.js」文件存在且可訪問,瀏覽器將產生一個新的線程,以異步方式下載文件。 下載完成後,它將被執行,工做人員將開始工做。
若是提供的文件路徑返回404,工做人員將自動失敗。
爲了啓動建立的worker,你須要調用postMessage方法:
worker.postMessage();
爲了在Web Worker和建立它的頁面之間進行通訊,您須要使用postMessage方法或廣播頻道。
較新的瀏覽器支持將JSON對象做爲該方法的第一個參數,而舊版瀏覽器只支持一個字符串。
讓咱們看一個建立worker的頁面如何與它進行通訊的例子,經過傳遞一個JSON對象做爲一個更「複雜」的例子。 傳遞一個字符串是徹底同樣的。
咱們來看看下面的HTML頁面(或者更精確一些):
<button onclick="startComputation()">Start computation</button> <script> function startComputation() { worker.postMessage({'cmd': 'average', 'data': [1, 2, 3, 4]}); } var worker = new Worker('doWork.js'); worker.addEventListener('message', function(e) { console.log(e.data); }, false); </script>
這就是咱們的工做人員腳本的外觀:
self.addEventListener('message', function(e) { var data = e.data; switch (data.cmd) { case 'average': var result = calculateAverage(data); // Some function that calculates the average from the numeric array. self.postMessage(result); break; default: self.postMessage('Unknown command'); } }, false);
點擊按鈕後,將從主頁面調用postMessage。 worker.postMessage行將JSON對象傳遞給worker,並添加cmd和數據鍵及其各自的值。 工做人員將經過定義的消息處理程序處理該消息。
當消息到達時,實際的計算正在工做者中執行,而不會阻塞事件循環。 工做人員正在檢查傳遞的事件e並執行,就像標準的JavaScript函數同樣。 完成後,結果會傳回主頁面。
在一名工人的背景下,自我和這一點都指向了工人的全球範圍。
有兩種方法能夠阻止工做人員:經過從主頁面調用worker.terminate()或在worker自己內部調用self.close()。
廣播頻道是更通用的通訊API。 它容許咱們將消息廣播到共享相同來源的全部上下文。 全部瀏覽器選項卡,iframe或從同一來源提供服務的工做人員均可以發送和接收消息:
// Connection to a broadcast channel var bc = new BroadcastChannel('test_channel'); // Example of sending of a simple message bc.postMessage('This is a test message.'); // Example of a simple event handler that only // logs the message to the console bc.onmessage = function (e) { console.log(e.data); } // Disconnect the channel bc.close()
在視覺上,你能夠看到廣播頻道的樣子,使其更加清晰:
雖然廣播頻道的瀏覽器支持有限,
有兩種方式將消息發送給Web Workers:
因爲Web Worker的多線程特性,Web Worker只能訪問JavaScript特性的一個子集。如下是功能列表:
可悲的是,Web Workers沒有訪問一些很是關鍵的JavaScript特性:
這意味着Web Worker不能操縱DOM(以及UI)。它有時可能會很是棘手,可是一旦您學會如何正確使用Web Workers,您將開始將它們做爲單獨的「計算機」使用,而全部UI更改將發生在您的頁面代碼中。工做人員將爲您完成全部繁重的工做,一旦工做完成,您會將結果傳遞給對UI進行必要更改的頁面。
與任何JavaScript代碼同樣,您須要處理在Web Workers中引起的任何錯誤。若是在執行工做時發生錯誤,則會觸發ErrorEvent。該接口包含三個有用的屬性,用於肯定出錯的地方:
這是一個例子:
function onError(e) { console.log('Line: ' + e.lineno); console.log('In: ' + e.filename); console.log('Message: ' + e.message); } var worker = new Worker('workerWithError.js'); worker.addEventListener('error', onError, false); worker.postMessage(); // Start worker without a message.
self.addEventListener('message', function(e) { postMessage(x * 2); // Intentional error. 'x' is not defined. };
在這裏,您能夠看到咱們建立了一geWorker並開始監聽錯誤事件。
在worker的內部(在workerWithError.js中),咱們經過將x乘以2來建立一個有意的異常,而未在該範圍中定義x。 異常傳播到初始腳本,而且正在調用有關錯誤信息的onError。
到目前爲止,咱們列出了Web Workers的優點和侷限性。如今讓咱們看看最佳應用場景: