JavaScript是如何工做的:Web Workers的構建塊+ 5個使用他們的場景

圖片描述

這是專門探索 JavaScript 及其所構建的組件的系列文章的第7篇。javascript

想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等着你!html

若是你錯過了前面的章節,能夠在這裏找到它們:前端

  1. JavaScript是如何工做的:引擎,運行時和調用堆棧的概述!
  2. JavaScript是如何工做的:深刻V8引擎&編寫優化代碼的5個技巧!
  3. JavaScript如何工做:內存管理+如何處理4個常見的內存泄漏 !
  4. JavaScript是如何工做的:事件循環和異步編程的崛起+ 5種使用 async/await 更好地編碼方式!
  5. JavaScript是如何工做: 深刻探索 websocket 和HTTP/2與SSE +如何選擇正確的路徑!
  6. JavaScript是如何工做的:與 WebAssembly比較 及其使用場景 !

此次咱們會逐步講解 Web Workers,先說個簡單的概念,接着討論不一樣類型的 Web Workers,他們的組成部分是如何一塊兒工做的,以及不一樣場景下它們各自優點和限制。最後,提供5個正確使用 Web Workers 的場景。java

正如咱們前面文章討論的那樣,你應該知道 JavaScript 語言採用的是單線程模型。然而,JavaScript 也爲開發人員提供了編寫異步代碼的機會。git

異步編程的侷限性

之前的文章討論過異步編程,以及應該在何時使用它。github

異步編程可讓UI界面是響應式(渲染速度快)的,經過"代碼調度",讓須要請求時間的代碼先放到在 event loop中晚一點再執行,這樣就容許UI先行渲染展現。web

異步編程的一個很好的用例就 AJAX 請求。因爲請求可能花費大量時間,所以可使用異步請求,在客戶端等待響應的同時還能夠執行其餘代碼。算法

圖片描述

然而,這帶來了一個問題——請求是由瀏覽器的WEB API處理的,可是如何使其餘代碼是異步的呢?例如,若是成功回調中的代碼很是佔用CPU:編程

var result = performCPUIntensiveCalculation();

若是 performCPUIntensiveCalculation 不是一個HTTP請求而是一個阻塞代碼(好比一個內容不少的for loop循環),就沒有辦法及時清空事件循環,瀏覽器的 UI 渲染就會被阻塞,頁面沒法及時響應給用戶。segmentfault

這意味着異步函數只能解決一小部分 JavaScript 語言單線程中的侷限性問題。

在某些狀況下,可使用 setTimeout 對長時間運行的計算阻塞的,可使用 setTimeout暫時放入異步隊列中,從讓頁面獲得更快的渲染。例如,經過在單獨的 setTimeout 調用中批處理複雜的計算,能夠將它們放在事件循環中單獨的「位置」上,這樣能夠爭取爲 UI 渲染/響應的執行時間。

看一個簡單的函數,計算一個數字數組的平均值:

如下是重寫上述代碼並「模擬」異步性的方法:

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 Worker 能夠解決這個問題

HTML5爲咱們帶來了不少新的東西,包括:

  • SSE(咱們在前一篇文章中已經描述並與WebSockets進行了比較)
  • Geolocation
  • Application cache
  • Local Storage
  • Drag and Drop
  • Web Workers

Web Worker 概述

Web Worker 的做用,就是爲 JavaScript 創造多線程環境,容許主線程建立 Worker 線程,將一些任務分配給後者運行。在主線程運行的同時,Worker 線程在後臺運行,二者互不干擾。等到 Worker 線程完成計算任務,再把結果返回給主線程。這樣的好處是,一些計算密集型或高延遲的任務,被 Worker 線程負擔了,主線程(一般負責 UI 交互)就會很流暢,不會被阻塞或拖慢。

你可能會問:「JavaScript不是一個單線程的語言嗎?」

事實上 JavaScript 是一種不定義線程模型的語言。Web Workers 不是 JavaScript 的一部分,而是能夠經過 JavaScript 訪問的瀏覽器特性。歷史上,大多數瀏覽器都是單線程的(固然,這已經改變了),大多數 JavaScript 實現都入發生在瀏覽器中。Web Workers 不是在 Node.JS 中實現的。Node.js 中有相似的集羣(cluster)、子進程概念(child_process),他們也是多線程可是和 Web Workers 仍是有區別 。

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

Dedicated Workers

專用 Workers 只能被建立它的頁面訪問,而且只能與它通訊。如下是瀏覽器支持的狀況:

圖片描述

Shared Workers

共享 Workers 在同一源(origin)下面的各類進程均可以訪問它,包括:iframes、瀏覽器中的不一樣tab頁(一個tab頁就是一個單獨的進程,因此Shared Workers能夠用來實現 tab 頁之間的交流)、以及其餘的共享 Workers。如下是瀏覽器支持的狀況:

圖片描述

Service workers

Service Worker 功能:

  • 後臺消息傳遞
  • 網絡代理,轉發請求,僞造響應
  • 離線緩存
  • 消息推送

在目前階段,Service Worker 的主要能力集中在網絡代理和離線緩存上。具體的實現上,能夠理解爲 Service Worker 是一個能在網頁關閉時仍然運行的 Web Worker。如下是瀏覽器支持的狀況:

Service Workers browser support

本文主要討論 專用 Workers,沒有特別聲明的話,Web Workers、Workers都是指代的專用 Workers。

Web Workers 是如何工做

Web Workers 通常經過腳本爲 .js 文件來構建,在頁面中還經過了一些異步的 HTTP 請求,這些請求是徹底被隱藏了的,你只須要調用 Web Worker API.

Worker 利用類線程間消息傳遞來實現並行性。它們保證界面的實時性、高性能和響應性呈現給用戶。

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

讓咱們看看基本 Workers 是如何建立的:

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

Worker() 構造函數的參數是一個腳本文件,該文件就是 Worker 線程所要執行的任務。因爲 Worker 不能讀取本地文件,因此這個腳本必須來自網絡。若是下載沒有成功(好比404錯誤),Worker 就會默默地失敗。

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

worker.postMessage();

Web Worker 通訊

爲了在 Web Worker 和建立它的頁面之間進行通訊,須要使用 postMessage 方法或 Broadcast Channel

postMessage 方法

新瀏覽器支持JSON對象做爲方法的第一個參數,而舊瀏覽器只支持字符串。

來看一個示例,經過將 JSON 對象做爲一個更「複雜」的示例傳遞,建立 Worker 的頁面如何與之通訊。傳遞字符串跟傳遞對象的方式也是同樣的。

讓咱們來看看下面的 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>

而後這是 worker 中的 js 代碼:

self.addEventListener('message', function(e) {
  var data = e.data;
  switch (data.cmd) {
    case 'average':
      var result = calculateAverage(data); // 從數值數組中計算平均值的函數
      self.postMessage(result);
      break;
    default:
      self.postMessage('Unknown command');
  }
}, false);

當單擊該按鈕時,將從主頁調用 postMessage。postMessage 行將 JSON 對象傳給Worker。Worker 經過定義的消息處理程序監聽並處理該消息。

當消息到達時,實際的計算在worker中執行,而不會阻塞事件循環。Worker 檢查傳遞的事件參數 e,像執行 JavaScript 函數同樣,處理完成後,把結果傳回給主頁。

在 Worker 做用域中,this 和 self 都指向 Worker 的全局做用域。

有兩種方法能夠中止 Worker:從主頁調用 worker.terminate() 或在 worker 內部調用 self.close()

Broadcast Channel

Broadcast Channel API 容許同一原始域和用戶代理下的全部窗口,iFrames 等進行交互。也就是說,若是用戶打開了同一個網站的的兩個標籤窗口,若是網站內容發生了變化,那麼兩個窗口會同時獲得更新通知。

仍是不明白?就拿 Facebook 做爲例子吧,假如你如今已經打開 了Facebook 的一個窗口,可是你此時尚未登陸,此時你又打開另一個窗口進行登陸,那麼你就能夠通知其餘窗口/標籤頁去告訴它們一個用戶已經登陸了並請求它們進行相應的頁面更新。

// 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()

能夠從下面這張圖,在視覺上來清晰地感覺 Broadcast Channel:

圖片描述

Broadcast Channel 瀏覽器支持比較有限:

圖片描述

消息的大小

有兩種方式發送消息給Web Workers:

  • 複製消息:消息被序列化、複製、發送,而後在另外一端反序列化。頁面和 Worker 不共享相同的實例,所以最終的結果是每次傳遞都會建立一個副本大多數瀏覽器,在兩邊都是使用的JSON對值進行編碼和解碼,這樣對數據的解碼、編碼操做,勢必會增長消息傳輸過程的時間開銷。信息越大,發送的時間就越長。
  • 傳遞消息:這意味着原始發送方在一旦發送後不能再使用它。傳輸數據幾乎是瞬間的,這種傳輸方式的侷限性在於只能用 ArrayBuffer 類型來傳遞。

Web Workers 可用的特性

因爲 JavaScript的多線程特性,Web工做者只能訪問JavaScript特性的一個子集。如下是它的一些特色:

Web Workers 因爲具備多線程特性,所以只能訪問 JavaScript 特性的子集。 如下是可以使用特性列表:

  • navigator 對象
  • location 對象(只讀)
  • MLHttpRequest
  • setTimeout()/clearTimeout() and setInterval()/clearInterval()
  • 應用緩存(Application Cache)
  • 使用 importScripts() 導入外部腳本
  • 建立其餘的 Web Workers

Web Workers 的侷限性

遺憾的是,Web Workers 沒法訪問一些很是關鍵的 JavaScript 特性:

  • DOM(它會形成線程不安全)
  • window 對象
  • document 對象
  • parent 對象

這意味着 Web Worker 不能操做 DOM (所以也不能操做 UI)。有時這可能很棘手,可是一旦你瞭解瞭如何正確使用 Web Workers,你就會開始將它們做爲單獨的「計算機」使用,而全部 UI 更改都將發生在你的頁面代碼中。 Workers 將爲你完成全部繁重的工做,而後一旦完成再把結果返回給 page 頁面。

處理錯誤

和 JavaScript 代碼同樣,Web workers 裏拋出的錯誤,你也須要進行處理。當 Worker 執行過程當中若是遇到錯誤,會觸發一個 ErrorEvent 事件。接口包含了三個有用的屬性來幫忙排查問題:

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

例子以下:

圖片描述

在這裏,能夠看到咱們建立了一個 worker 並開始偵聽錯誤事件。

圖片描述

在 worker 內部(在 workerWithError.js 中),咱們經過將未定義 x 乘以 2 來建立一個異常。異常被傳播到初始腳本,而後經過頁面監聽 error事件,對錯誤進行捕獲。

5個好的 Web Workers 應用實例

到目前爲止,咱們已經列出了 Web Workers 的優勢和侷限性。如今讓咱們看看它們最強大的用例是什麼:

  • Ray tracing(光線追蹤):光線追蹤是一種以像素爲單位跟蹤光的路徑生成圖像的渲染技術。光線追蹤利用 CPU 密集型的數學計算來模擬光的路徑。其思想是模擬一些效果,如反射、折射、材料等。全部這些計算邏輯均可以添加到 Web Worker 中,以免阻塞 UI線程。更好的是——能夠很容易地在多個 workers 之間(以及在多個cpu之間)分割圖像呈現。下面是一個使用 Web Workers 的光線追蹤的簡單演示—https://nerget.com/rayjs-mt/r...
  • Encryption(加密):因爲對我的和敏感數據的監管愈來愈嚴格,端到端加密愈來愈受歡迎。加密是一件很是耗時的事情,特別是若是有不少數據須要頻繁加密(例如,在發送到服務器以前)。這是一個使用 Web Worker 很是好的場景,由於它不須要訪問 DOM 或任何花哨的東西——它是完成其工做的純算法。只要是在 Web Worker 中工做的,對於端用戶就是無縫的,不會影響到體驗。
  • Prefetching data(預取數據):爲了優化你的網站或 web 應用程序並改進數據加載時間,你能夠利用 Web Workers 提早加載和存儲一些數據,以便在須要時稍後使用。Web Workers 在這種狀況下很是棒,由於它們不會影響應用程序的UI,這與不使用Workers 時是不一樣的。
  • Progressive Web Apps(漸進式Web應用程序):這種漸進式Web應用程序要求,即便在用戶網絡不穩定的條件下,也可以迅速的加載。這意味着數據必須本地存儲在瀏覽器中。這也是 IndexDB 或相似 api 發揮做用的地方。一般狀況下,客戶端的存儲都是必要的,但使用起來須要不阻塞UI渲染線程,那麼工做就須要在 Worker 中進行了。不過,以IndexDB 爲例,它提供了一些異步的API,調用它們的話也不須要使用 web worker,但若是是同步的 API,就必需要在 Worker 中使用了。
  • Spell checking(拼寫檢查):一個基本的拼寫檢查程序的工做流程以下-程序讀取一個字典文件與一個正確拼寫單詞列表。字典被解析爲一個搜索樹,以使實際的文本搜索更有效。當一個單詞被提供給檢查器時,程序檢查它是否存在於預先構建的搜索樹中。若是在樹中沒有找到該單詞,能夠經過替換替換字符並測試它是不是有效的單詞(若是是用戶想要寫的單詞),爲用戶提供替代拼寫。全部的這些處理過程均可以在 Web Worker中進行了,用戶能夠不被阻塞的輸入詞彙和句子,Web Worker 在後臺校驗詞彙是否正確以及提供備選詞彙。

原文:

https://blog.sessionstack.com...

代碼部署後可能存在的BUG無法實時知道,過後爲了解決這些BUG,花了大量的時間進行log 調試,這邊順便給你們推薦一個好用的BUG監控工具Fundebug

你的點贊是我持續分享好東西的動力,歡迎點贊!

歡迎加入前端你們庭,裏面會常常分享一些技術資源。

clipboard.png

相關文章
相關標籤/搜索