JavaScript工做原理(七):Web Workers的構建快和5種使用場景

這一次咱們將分拆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函數,該函數將在事件循環的更下方添加計算的每一個步驟。 在每次計算之間,將有足夠的時間進行其餘計算,這是解凍瀏覽器所必需的。

Web Workers 扭轉局面

HTML5給咱們帶來了不少很棒的東西,包括:

  • SSE(咱們已經在以前的文章中描述並與WebSockets進行了比較)
  • 地理位置
  • 應用程序緩存
  • 本地存儲
  • 拖放
  • Web Worker

Web Workers是瀏覽器內的線程,可用於執行JavaScript代碼而不會阻止事件循環。

這真是太神奇了。 JavaScript的整個範例基於單線程環境的思想,但在這裏來自網絡工做者(Web Workers),它解除了(部分)這種限制。

Web Workers容許開發人員將長時間運行和計算密集型任務放在後臺,而不會阻止用戶界面,從而使您的應用程序更具響應能力。更重要的是,爲了解決事件循環的問題,不須要使用setTimeout技巧。

下面是一個簡單的演示,顯示了在有和沒有Web Workers的狀況下對數組進行排序的區別。

Web Worker概述

Web Workers容許您執行諸如啓動長時間運行的腳原本處理計算密集型任務,但不會阻止UI。實際上,這一切都是平行進行的。Web Workers真正是多線程的。

你可能會說 - 「JavaScript不是單線程語言嗎?」。

當你意識到JavaScript是一種沒有定義線程模型的語言時,這應該是你的'aha!'時刻。 Web Workers不是JavaScript的一部分,它們是能夠經過JavaScript訪問的瀏覽器功能。大多數瀏覽器從來都是單線程的(固然,這已經改變了),而且大多數JavaScript實現都發生在瀏覽器中。 Web Worker沒有在Node.JS中實現 - 它有一個「cluster」或「child_process」的概念,有點不一樣。

值得注意的是,這個規範提到了三種類型的Web Workers:

  • Dedicated Worker
  • Shared Worker
  • Service Worker

Dedicated Worker

專用Web Worker由主進程實例化,而且只能與其進行通訊。
圖片描述

Shared Worker

共享工做人員能夠經過運行在同一來源的全部進程(不一樣的瀏覽器選項卡,iframe或其餘共享工做人員)訪問。
圖片描述

Service Worker

服務工做人員是一個事件驅動的工做人員,針對原點和路徑進行了註冊。 它能夠控制與之關聯的網頁/網站,攔截並修改導航和資源請求,並以很是細化的方式緩存資源,從而使您能夠很好地控制應用在某些狀況下的行爲方式(例如,當網絡不是可用。)
圖片描述

在這篇文章中,咱們將關注Dedicated Workers(專職工做者)並將他們稱爲「(Web Workders)網絡工做者」或「(Workers)工做者」。

Web Workders 如何工做

Web Workers被實現爲.js文件,這些文件經過頁面中的異步HTTP請求提供。 Web Worker API徹底隱藏了這些請求。

工做人員利用相似線程的消息傳遞來實現並行性。 它們很是適合保持您的用戶界面的最新性,性能和響應能力。

Web工做人員在瀏覽器中的獨立線程中運行。 所以,它們執行的代碼須要包含在單獨的文件中。 記住這一點很是重要。

讓咱們看看如何建立一個基本的工做人員:

var worker = new Worker('task.js');

若是「task.js」文件存在且可訪問,瀏覽器將產生一個新的線程,以異步方式下載文件。 下載完成後,它將被執行,工做人員將開始工做。
若是提供的文件路徑返回404,工做人員將自動失敗。

爲了啓動建立的worker,你須要調用postMessage方法:

worker.postMessage();

網絡工做者通訊

爲了在Web Worker和建立它的頁面之間進行通訊,您須要使用postMessage方法或廣播頻道。

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:

  • 複製消息:消息被序列化,複製,發送,而後在另外一端解除序列化。頁面和工做人員不共享同一個實例,因此最終結果是每次傳遞都會建立一個副本。大多數瀏覽器經過自動JSON編碼/解碼任何一端的值來實現此功能。正如所料,這些數據操做爲消息傳輸增長了大量的開銷。信息越大,發送時間越長。
  • 轉發郵件:這意味着原始發件人在發送後不能再使用它。傳輸數據幾乎是瞬間的。限制是只有ArrayBuffer能夠轉讓。

Web Workers可用的功能

因爲Web Worker的多線程特性,Web Worker只能訪問JavaScript特性的一個子集。如下是功能列表:

  • navigator對象
  • location對象(只讀)
  • XMLHttpRequest
  • setTimeout()/ clearTimeout()和setInterval()/ clearInterval()
  • 應用程序緩存
  • 使用importScripts()導入外部腳本
  • 建立其餘Web Workers

Web Worker限制

可悲的是,Web Workers沒有訪問一些很是關鍵的JavaScript特性:

  • DOM(它不是線程安全的)
  • 窗口對象
  • 文檔對象
  • 父對象

這意味着Web Worker不能操縱DOM(以及UI)。它有時可能會很是棘手,可是一旦您學會如何正確使用Web Workers,您將開始將它們做爲單獨的「計算機」使用,而全部UI更改將發生在您的頁面代碼中。工做人員將爲您完成全部繁重的工做,一旦工做完成,您會將結果傳遞給對UI進行必要更改的頁面。

處理錯誤

與任何JavaScript代碼同樣,您須要處理在Web Workers中引起的任何錯誤。若是在執行工做時發生錯誤,則會觸發ErrorEvent。該接口包含三個有用的屬性,用於肯定出錯的地方:

  • filename - 致使錯誤的工做者腳本的名稱
  • lineno - 發生錯誤的行號
  • message - 錯誤的描述

這是一個例子:

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的應用場景

到目前爲止,咱們列出了Web Workers的優點和侷限性。如今讓咱們看看最佳應用場景:

  • 光線追蹤:光線追蹤是一種經過將光線追蹤爲像素來生成圖像的渲染技術。光線追蹤使用CPU密集型數學計算來模擬光線路徑。這個想法是模擬反射,折射,材質等一些效果。全部這些計算邏輯均可以添加到Web Worker中以免阻塞UI線程。更好的是 - 您能夠輕鬆地將幾個工做人員(以及多個CPU之間)之間的圖像渲染分開。這裏是使用Web Workers進行光線追蹤的簡單演示 - https://nerget.com/rayjs-mt/r...
  • 加密:因爲對我的和敏感數據的監管日益嚴格,端到端加密愈來愈受歡迎。加密多是一件很是耗時的事情,特別是若是有不少數據必須常常加密(例如在將數據發送到服務器以前)。這是一個很是好的場景,可使用Web Worker,由於它不須要訪問DOM或任何想象的東西 - 這是純粹的算法來完成他們的工做。一旦進入工做人員,它對最終用戶而言是無縫的,而且不會影響他們的體驗。
  • 預取數據:爲了優化您的網站或Web應用程序並縮短數據加載時間,您能夠利用Web Workers預先加載和存儲一些數據,以便稍後在須要時使用它。在這種狀況下,Web Workers是驚人的,由於它們不會影響您的應用的用戶界面,而不像工做時沒有工做人員那樣。
  • 漸進式Web應用程序:即便網絡鏈接不穩定,它們也必須快速加載。這意味着數據必須存儲在本地瀏覽器中。這是IndexDB或相似的API進場的地方。基本上,須要客戶端存儲。爲了在不阻塞UI線程的狀況下使用,工做必須在Web Workers中完成。那麼,就IndexDB而言,有一個異步API,即便沒有工做人員也能夠執行此操做,但以前有一個同步API(可能會再次引入),只能在工做人員中使用。
  • 拼寫檢查:基本拼寫檢查程序按如下方式工做 - 程序讀取帶有拼寫正確單詞列表的字典文件。正在將字典解析爲搜索樹以使實際的文本搜索效率更高。當一個單詞被提供給檢查器時,程序檢查它是否存在於預先構建的搜索樹中。若是在樹中沒有找到該單詞,則能夠經過替代字符並測試它是不是有效的單詞 - 若是它是用戶想要寫的單詞來爲用戶提供替代拼寫。全部這些處理均可以輕鬆卸載到Web Worker中,以便用戶能夠在沒有任何UI阻塞的狀況下鍵入單詞和句子,而工做人員則執行全部搜索和提供建議。
相關文章
相關標籤/搜索