首頁  >  文章  >  web前端  >  JavaScript Web Workers的建構塊及5個使用場景

JavaScript Web Workers的建構塊及5個使用場景

coldplay.xixi
coldplay.xixi轉載
2020-12-04 15:03:281938瀏覽

這是JavaScript欄位專門探索javascript及其所建構的元件的系列文章。

JavaScript Web Workers的建構塊及5個使用場景

相關免費學習推薦:javascript(影片)

這次我們會逐步解釋Web Workers,先說個簡單的概念,接著討論不同類型的Web Workers,他們的組成部分是如何一起工作的,以及不同場景下它們各自優勢和限制。最後,提供5個正確使用 Web Workers 的場景。

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

非同步編程的限制

先前的文章討論過非同步編程,以及應該在什麼時候使用它。

非同步程式設計可以讓UI介面是響應式(渲染速度快)的,透過"程式碼調度",讓需要請求時間的程式碼先放到在event loop中晚一點再執行,這樣就允許UI先行渲染展示。

非同步程式設計的一個很好的用例就 AJAX 請求。由於請求可能花費大量時間,因此可以使用非同步請求,在客戶端等待回應的同時也可以執行其他程式碼。

JavaScript Web Workers的建構塊及5個使用場景

然而,這帶來了一個問題——請求是由瀏覽器的WEB API處理的,但是如何使其他程式碼是異步的呢?例如,如果成功回呼中的程式碼非常佔用CPU:

var result = performCPUIntensiveCalculation();

如果performCPUIntensiveCalculation 不是一個HTTP請求而是一個阻塞程式碼(例如一個內容很多的for loop循環),就沒有辦法及時清空事件循環,瀏覽器的UI 渲染就會被阻塞,頁面無法及時回應給使用者。

這意味著非同步函數只能解決一小部分 JavaScript 語言單執行緒中的限制問題。

在某些情況下,可以使用setTimeout 對長時間運行的計算阻塞的,可以使用setTimeout暫時放入非同步佇列中,從讓頁面得到更快的渲染。例如,透過在單獨的setTimeout 呼叫中批次複雜的計算,可以將它們放在事件循環中單獨的「位置」上,這樣可以爭取為UI 渲染/回應的執行時間。

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

JavaScript Web Workers的建構塊及5個使用場景

#以下是重寫上述程式碼並「模擬」非同步性的方法:

function averageAsync(numbers, callback) {
    var len = numbers.length,
        sum = 0;

    if (len === 0) {
        return 0;
    } 

    function calculateSumAsync(i) {
        if (i <p>使用setTimeout函數,該函數將在事件循環中進一步新增計算的每個步驟。在每次計算之間,將有足夠的時間進行其他計算,從而可以讓瀏覽器進行渲染。 </p><h2>Web Worker 可以解決這個問題</h2><p>HTML5為我們帶來了很多新的東西,包括:</p>
  • 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:

  • 專用Workers (Dedicated Workers)
  • 共享Workers (Shared Workers)
  • 服務Workers (Service workers)

Dedicated Workers

專用Workers 只能被創建它的頁面訪問,並且只能與它通訊。以下是瀏覽器支援的情況:

JavaScript Web Workers的建構塊及5個使用場景

Shared Workers

共享 Workers 在同一源(origin)下面的各种进程都可以访问它,包括:iframes、浏览器中的不同tab页(一个tab页就是一个单独的进程,所以Shared Workers可以用来实现 tab 页之间的交流)、以及其他的共享 Workers。以下是浏览器支持的情况:

JavaScript Web Workers的建構塊及5個使用場景

Service workers

Service Worker 功能:

  • 后台消息传递
  • 网络代理,转发请求,伪造响应
  • 离线缓存
  • 消息推送

在目前阶段,Service Worker 的主要能力集中在网络代理和离线缓存上。具体的实现上,可以理解为 Service Worker 是一个能在网页关闭时仍然运行的 Web Worker。以下是浏览器支持的情况:

JavaScript Web Workers的建構塊及5個使用場景

本文主要讨论 专用 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>Start computation</button>

<script>
  function startComputation() {
    worker.postMessage({&#39;cmd&#39;: &#39;average&#39;, &#39;data&#39;: [1, 2, 3, 4]});
  }
  var worker = new Worker(&#39;doWork.js&#39;);
  worker.addEventListener(&#39;message&#39;, 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:

JavaScript Web Workers的建構塊及5個使用場景

Broadcast Channel 浏览器支持比较有限:

JavaScript Web Workers的建構塊及5個使用場景

消息的大小

有两种方式发送消息给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 - 對錯誤的描述
#範例如下:

JavaScript Web Workers的建構塊及5個使用場景##在這裡,可以看到我們建立了一個worker 並開始偵聽錯誤事件。

JavaScript Web Workers的建構塊及5個使用場景在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 在後台校驗詞彙是否正確以及提供備選詞彙。

想了解更多程式設計學習,請關注php培訓##欄位!

以上是JavaScript Web Workers的建構塊及5個使用場景的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:segmentfault.com。如有侵權,請聯絡admin@php.cn刪除