摘要:最后,我們將會(huì)介紹個(gè)的使用場景。異步編程的局限性前面我們了解到異步編程及其使用時(shí)機(jī)。請(qǐng)求是一個(gè)很好的異步編程的使用場景。整個(gè)是基于單線程環(huán)境的而部分可以突破這方面的限制。最佳使用場景迄今為止,我們列舉了的長處及其限制。
Web Workers 分類及 5 個(gè)使用場景
原文請(qǐng)查閱這里,略有刪減,本文采用知識(shí)共享署名 4.0 國際許可協(xié)議共享,BY Troland。
這是 JavaScript 工作原理的第七章。
本系列持續(xù)更新中,Github 地址請(qǐng)查閱這里。
現(xiàn)在,我們將會(huì)剖析 Web Workers:我們將會(huì)綜合比較不同類型的 workers,如何組合運(yùn)用他們的構(gòu)建模塊來進(jìn)行開發(fā)以及不同場景下各自的優(yōu)缺點(diǎn)。最后,我們將會(huì)介紹 5 個(gè) Web Workder 的使用場景。
在前面的詳細(xì)介紹的文章中你已經(jīng)清楚地了解到 JavaScript 是單線程的事實(shí)。然而,JavaScript 也允許開發(fā)者編寫異步代碼。
異步編程的局限性前面我們了解到異步編程及其使用時(shí)機(jī)。
異步編程通過調(diào)度部分代碼使之在事件循環(huán)中延遲執(zhí)稈,這樣就允許優(yōu)先渲染程序界面,從而讓程序運(yùn)行流暢。
AJAX 請(qǐng)求是一個(gè)很好的異步編程的使用場景 。因?yàn)檎?qǐng)求可能會(huì)花很長的時(shí)間,所以可以異步執(zhí)行它們,然后在客戶端等待數(shù)據(jù)返回的同時(shí),運(yùn)行其它代碼。
// 假設(shè)使用 jQuery jQuery.ajax({ url: "https://api.example.com/endpoint", success: function(response) { // 當(dāng)數(shù)據(jù)返回時(shí)候的代碼 } });
然而,這里會(huì)產(chǎn)生一個(gè)問題-AJAX 請(qǐng)求是由瀏覽器網(wǎng)頁 API 進(jìn)行處理的,可以異步執(zhí)行其它代碼嗎?比如,假設(shè)成功回調(diào)的代碼是 CPU 密集型的:
var result = performCPUIntensiveCalculation();
如果 performCPUIntensiveCalculation 不是一個(gè) HTTP 請(qǐng)求而是一個(gè)會(huì)阻塞界面渲染的代碼(比如大量的 for 循環(huán)),這樣就沒有辦法釋放事件循環(huán)和瀏覽器的 UI-瀏覽器會(huì)被凍結(jié)住且失去響應(yīng)。
這意味著,異步函數(shù)只是是解決了一部分 JavaScript 的單線程限制。
在某些情況下,你可以通過使用 setTimeout 來很好地解決由于長時(shí)間計(jì)算所造成的 UI 阻塞。比如,通過把一個(gè)復(fù)雜的計(jì)算批量拆分為若干個(gè)setTimeout 調(diào)用 ,把它們放在事件循環(huán)的不同位置執(zhí)行,然后這樣就可以使得 UI 有時(shí)間進(jìn)行渲染及響應(yīng)。
讓我們看一個(gè)計(jì)算數(shù)值數(shù)組的平均值的簡單函數(shù)。
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) { // 在事件循環(huán)中調(diào)用下一個(gè)函數(shù) setTimeout(function() { sum += numbers[i]; calculateSumAsync(i + 1); }, 0); } else { // 到達(dá)數(shù)組末尾,調(diào)用回調(diào) callback(sum / len); } } calculateSumAsync(0); }
這里利用 setTimeout 函數(shù)在事件循環(huán)中循序添加每一次計(jì)算。在每一次計(jì)算之間,將會(huì)有充足的時(shí)間來進(jìn)行其它的計(jì)算和解凍瀏覽器。
Web Workders 來救場HTML5 給我們帶了很多開箱即用的好用的功能,包括:
SSE(之前文章中提到過并且和 WebSockets 進(jìn)行了比較)
Geolocation
Application cache
Local Storage
Drag and Drop
Web Workers
Web Workers 是瀏覽器內(nèi)置的線程所以可以被用來執(zhí)行非阻塞事件循環(huán)的 JavaScript 代碼。
屌爆了。整個(gè) JavaScript 是基于單線程環(huán)境的而 Web Workers (部分)可以突破這方面的限制。
Web Workers 允許開發(fā)者把長時(shí)間運(yùn)行和密集計(jì)算型的任務(wù)放在后臺(tái)執(zhí)行而不會(huì)阻塞 UI,這會(huì)使得應(yīng)用程序運(yùn)行得更加流暢。另外,這樣就不用再使用 setTimeout 的黑科技來防止阻塞事件循環(huán)了。
這里有一個(gè)展示使用和未使用 Web Workers 來進(jìn)行數(shù)組排序的區(qū)別的示例。
Web Workers 概覽Web Workers 允許你做諸如運(yùn)行處理 CPU 計(jì)算密集型任務(wù)的耗時(shí)腳本而不會(huì)阻塞 UI 的事情。事實(shí)上,所有這些操作都是并行執(zhí)行的。Web Workers 是真正的多線程。
你或許會(huì)有疑問-『難道 JavaScript 不是單線程的嗎?』。
當(dāng)你意識(shí)到 JavaScript 是一門沒有定義線程模型的語言的時(shí)候,或許你會(huì)感覺非常的驚訝。Web Workers 并不是 JavaScript 的一部分,他們是可以通過 JavaScript 進(jìn)行操作的瀏覽器功能之一。以前,大多數(shù)的瀏覽器是單線程的(當(dāng)然,現(xiàn)在已經(jīng)變了),而且大多數(shù)的 JavaScript 功能是在瀏覽器端實(shí)現(xiàn)完成的。Node.js 沒有實(shí)現(xiàn) Web Workers -它有 『cluster』和 『child_process』的概念,這兩者和 Web Workers 有些許差異。
值得注意的是,規(guī)范中有三種類型的 Web Workers:
Dedicated Workers
Shared Workers
Service workers
Dedicated WorkersDedicated Web Workers 是由主進(jìn)程實(shí)例化并且只能與之進(jìn)行通信
Shared workers 可以被運(yùn)行在同源的所有進(jìn)程訪問(不同的瀏覽的選項(xiàng)卡,內(nèi)聯(lián)框架及其它shared workers)。
Service Worker 是一個(gè)由事件驅(qū)動(dòng)的 worker,它由源和路徑組成。它可以控制它關(guān)聯(lián)的網(wǎng)頁,解釋且修改導(dǎo)航,資源的請(qǐng)求,以及一種非常細(xì)粒度的方式來緩存資源以讓你非常靈活地控制程序在某些情況下的行為(比如網(wǎng)絡(luò)不可用)。
本篇文章,我們將會(huì)專注于 Dedicated Workers 并以 『Web Workers』或者 『Workers』來稱呼它。
Web Workers 運(yùn)行原理Web Workers 是以加載 .js 文件的方式實(shí)現(xiàn)的,這些文件會(huì)在頁面中異步加載。這些請(qǐng)求會(huì)被 Web Worker API 完全隱藏。
Workers 使用類線程的消息傳輸-獲取模式。它們非常適合于為用戶提供最新的 UI ,高性能及流暢的體驗(yàn)。
Web Workers 運(yùn)行于瀏覽器的一個(gè)隔離線程之中。因此,他們所執(zhí)行的代碼必須被包含在一個(gè)多帶帶的文件之中。請(qǐng)謹(jǐn)記這一特性。
讓我們看如何創(chuàng)建初始化 worker 吧:
var worker = new Worker("task.js");
如果 『task.js』文件存在且可訪問,瀏覽器會(huì)生成一個(gè)線程來異步下載文件。當(dāng)下載完成的時(shí)候,文件會(huì)立即執(zhí)行然后 worker 開始運(yùn)行。萬一文件不存在,worker 會(huì)運(yùn)行失敗且沒有任何提示。
為了啟動(dòng)創(chuàng)建的 worker,你需要調(diào)用 postMessage 方法:
worker.postMessage();Web Worker 通信
為了在 Web Worker 和 創(chuàng)建它的頁面間進(jìn)行通信,你得使用 postMessage 方法或者一個(gè)廣播信道。
postMessage 方法最新的瀏覽器支持方法的第一參數(shù)為一個(gè) JSON 對(duì)象而舊的瀏覽器只支持字符串。
讓我們來看一個(gè)例子,通過往 worker 的方法的第一個(gè)參數(shù)傳入更為復(fù)雜的 JSON 對(duì)象來理解其創(chuàng)建者頁面是如何與之進(jìn)行來回通信的。傳入字符串與之類似。
讓我們看下以下的 HTML 頁面(或者更準(zhǔn)確地說是 HTML 頁面的一部分)
worker 的腳本如下:
self.addEventListener("message", function(e) { var data = e.data; switch (data.cmd) { case "average": var result = calculateAverage(data); // 某個(gè)數(shù)值數(shù)組中計(jì)算平均值的函數(shù) self.postMessage(result); break; default: self.postMessage("Unknown command"); } }, false);
當(dāng)點(diǎn)擊按鈕,會(huì)在主頁面調(diào)用 postMessage 方法。
worker.postMessage 行代碼會(huì)把包含 cmd 和 data 屬性及其各自屬性值的 JSON 對(duì)象傳入 worker。worker 通過定義監(jiān)聽 message 事件來處理傳過來的消息。
當(dāng)接收到消息的時(shí)候,worker 會(huì)執(zhí)行實(shí)際的計(jì)算而不會(huì)阻塞事件循環(huán)。worker 會(huì)檢查傳進(jìn)來的 e 事件,然后像一個(gè)標(biāo)準(zhǔn)的 JavaScript 函數(shù)那樣運(yùn)行。當(dāng)運(yùn)行結(jié)束,傳回主頁面計(jì)算結(jié)果。
在 worker 的上下文中,self 和 this 都指向 worker 的全局作用域。
有兩種方法來中斷 woker 的執(zhí)行:主頁面中調(diào)用 worker.terminate() 或者在 workder 內(nèi)部調(diào)用 self.close()廣播信道
Broadcast Channel 是更為普遍的通信接口。它允許我們向共享同一個(gè)源的所有上下文發(fā)送消息。同一個(gè)源下的所有的瀏覽器選項(xiàng)卡,內(nèi)聯(lián)框架或者 workers 都可以發(fā)送和接收消息:
// 連接到一個(gè)廣播信道 var bc = new BroadcastChannel("test_channel"); // 發(fā)送簡單信息示例 bc.postMessage("This is a test message."); // 一個(gè)在控制臺(tái)打印消息的簡單事件處理程序示例 // logs the message to the console bc.onmessage = function (e) { console.log(e.data); } // 關(guān)閉信道 bc.close()
視覺上看,你可以通過廣播信道的圖例以更加深刻的理解它。
然而,廣播信道瀏覽器兼容性不太好:
消息大小有兩種向 Web Workers 發(fā)送消息的方法:
復(fù)制消息:消息被序列化,復(fù)制,然后發(fā)送出去,接著在接收端反序列化。頁面和 worker 沒有共享一個(gè)相同的消息實(shí)例,所以在每次傳遞消息過程中最后的結(jié)果都是復(fù)制的。大多數(shù)瀏覽器是通過在任何一端自動(dòng)進(jìn)行 JSON 編碼/解碼消息值來實(shí)現(xiàn)這一功能。正如所預(yù)料的那樣,這些對(duì)于數(shù)據(jù)的操作顯著增加了消息傳送的性能開銷。消息越大,傳送的時(shí)間越長。
消息傳輸:這意味著最初的消息發(fā)送者一發(fā)送即不再使用()。數(shù)據(jù)傳輸非常的快。唯一的限制即只能傳輸 ArrayBuffer 數(shù)據(jù)對(duì)象。
Web Workers 的可用功能由于 Web Workers 的多線程特性,它只能使用一部分 JavaScript 功能。以下是可使用的功能列表:
navigator 對(duì)象
location 對(duì)象(只讀)
XMLHttpRequest
setTimeout()/clearTimeout() 和 setInterval()/clearInterval()
Application Cache
使用 importScripts 來引用外部腳本
創(chuàng)建其它 web workers
Web Worker 的局限性令人沮喪的是,Web Workers 不能夠訪問一些非常關(guān)鍵的 JavaScript 功能:
DOM(非線程安全的)
window 對(duì)象
document 對(duì)象
parent 對(duì)象
這意味著 Web Worker 不能夠操作 DOM(因此不能更新 UI)。有時(shí)候,這會(huì)讓人很蛋疼,不過一旦你學(xué)會(huì)如何合理地使 Web Workers,你就會(huì)把它當(dāng)成多帶帶的『計(jì)算機(jī)器』來使用而用其它頁面代碼來操作 UI。Workers 將會(huì)為你完成繁重的計(jì)算任務(wù)然后一旦任務(wù)完成,會(huì)把結(jié)果傳到頁面中并對(duì)界面進(jìn)行必要的更新。
錯(cuò)誤處理和任何 JavaScript 代碼一樣,你會(huì)想要處理 Web Workers 中的任何錯(cuò)誤。當(dāng)在 worker 執(zhí)行過程中有錯(cuò)誤發(fā)生的時(shí)候,會(huì)觸發(fā) ErrorEvent 事件。這個(gè)接口包含三個(gè)有用的屬性來指出錯(cuò)誤的地方:
filename-引起錯(cuò)誤的 worker 腳本名稱
lineno-引起錯(cuò)誤的代碼行數(shù)
message-錯(cuò)誤描述
示例:
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(); // 啟動(dòng) worker 而不帶任何消息
self.addEventListener("message", function(e) { postMessage(x * 2); // 意圖錯(cuò)誤. "x" 未定義 };
這里,你可以看到我們創(chuàng)建了一個(gè) worker 然后開始監(jiān)聽 error 事件。
在 worker 中(在 workerWithError 中),我們通過未在作用域中定義的 x 乘以 2 來創(chuàng)建一個(gè)意圖錯(cuò)誤。異常會(huì)傳播到初始化腳本(即主頁面中)然后調(diào)用 onError 并傳入關(guān)于錯(cuò)誤的信息。
Web Workers 最佳使用場景迄今為止,我們列舉了 Web Workers 的長處及其限制。讓我們看看他們的最佳使用場景:
射線追蹤:射線追蹤是一項(xiàng)通過追蹤光線的路徑作為像素來生成圖片的渲染技術(shù)。Ray tracing 使用 CPU 密集型計(jì)算來模仿光線的路徑。思路即模仿一些諸如反射,折射,材料等的效果。所有的這些計(jì)算邏輯可以放在 Web Worker 中以避免阻塞 UI 線程。甚至更好的方法即-你可以輕易地把把圖片的渲染拆分在幾個(gè) workers 中進(jìn)行(即在各自的 CPU 中進(jìn)行計(jì)算,意思是說利用多個(gè) CPU 來進(jìn)行計(jì)算,可以參考下 nodejs 的 api)。這里有一個(gè)使用 Web Workers 來進(jìn)行射線追蹤的簡單示例-h(huán)ttps://nerget.com/rayjs-mt/r...。
加密:端到端的加密由于對(duì)保護(hù)個(gè)人和敏感數(shù)據(jù)日益嚴(yán)格的法律規(guī)定而變得越來越流行。加密有時(shí)候會(huì)非常地耗時(shí),特別是如果當(dāng)你需要經(jīng)常加密很多數(shù)據(jù)的時(shí)候(比如,發(fā)往服務(wù)器前加密數(shù)據(jù))。這是一個(gè)使用 Web Worker 的絕佳場景,因?yàn)樗⒉恍枰L問 DOM 或者利用其它魔法-它只是純粹使用算法進(jìn)行計(jì)算而已。一旦在 worker 進(jìn)行計(jì)算,它對(duì)于用戶來說是無縫地且不會(huì)影響到用戶體驗(yàn)。
預(yù)取數(shù)據(jù):為了優(yōu)化網(wǎng)站或者網(wǎng)絡(luò)應(yīng)用及提升數(shù)據(jù)加載時(shí)間,你可以使用 Workers 來提前加載部分?jǐn)?shù)據(jù)以備不時(shí)之需。不像其它技術(shù),Web Workers 在這種情況下是最棒噠,因?yàn)樗粫?huì)影響程序的使用體驗(yàn)。
漸進(jìn)式網(wǎng)絡(luò)應(yīng)用:即使在網(wǎng)絡(luò)不穩(wěn)定的情況下,它們必須快速加載。這意味著數(shù)據(jù)必須本地存儲(chǔ)于瀏覽器中。這時(shí)候 IndexDB 及其它類似的 API 就派上用場了。大體上說,一個(gè)客戶端存儲(chǔ)是必須的。為了不阻塞 UI 線程的渲染,這項(xiàng)工作必須由 Web Workers 來執(zhí)行。呃,當(dāng)使用 IndexDB的時(shí)候,可以不使用 workers 而使用其異步接口,但是之前它也含有同步接口(可能會(huì)再次引入 ),這時(shí)候就必須在 workers 中使用 IndexDB。
這里需要注意的是在現(xiàn)代瀏覽器已經(jīng)不支持同步接口了,具體可查看這里。
拼寫檢查:一個(gè)基本的拼寫檢測器是這樣工作的-程序會(huì)讀取一個(gè)包含拼寫正確的單詞列表的字典文件。字典會(huì)被解析成一個(gè)搜索樹以加快實(shí)際的文本搜索。當(dāng)檢查器檢查一個(gè)單詞的時(shí)候,程序會(huì)在預(yù)構(gòu)建搜索樹中進(jìn)行檢索。如果在樹中沒有檢索到,則會(huì)通過提供替代的字符為用戶提供替代的拼寫并檢測單詞是否是有效-是否是用戶需要的單詞。這個(gè)檢索過程中的所有工作都可以交由 Web Worker 來完成,這樣用戶就只需輸入單詞和語句而不會(huì)阻塞 UI,與此同時(shí) worker 會(huì)處理所有的搜索和服務(wù)建議。
在 SessionStack 中對(duì)于我們來說性能和可靠性是至關(guān)重要的。之所以這么重要的原因是一旦把 SessionStack 整合進(jìn)網(wǎng)絡(luò)應(yīng)用,它就會(huì)開始收集從 DOM 變化,用戶交互到網(wǎng)絡(luò)請(qǐng)求,未處理異常和調(diào)試信息的所有一切信息。所有的數(shù)據(jù)都是即時(shí)傳輸?shù)轿覀兊姆?wù)器的,這樣就允許你以視頻的方式重放網(wǎng)絡(luò)應(yīng)用中的所有問題以及觀察用戶端產(chǎn)生的一切問題。所有的一切都只會(huì)給你的程序帶來極小的延遲且沒有任何的性能開銷。
這就是為什么我們使用 Web Workers 來處理監(jiān)視庫和播放器的邏輯的原因,因?yàn)?Web Workers 會(huì)幫我們處理諸如使用哈希來驗(yàn)證數(shù)據(jù)完整性,渲染等 CPU 密集型的任務(wù)。
在這個(gè)網(wǎng)絡(luò)技術(shù)日新月異的時(shí)代,我們更加努力地保證 SessionStack 輕巧且不會(huì)給用戶程序帶來任何性能影響。
擴(kuò)展實(shí)際工作過程會(huì)遇到用戶需要通過解析遠(yuǎn)程圖片來獲得圖片 base64 的案例,那么這時(shí)候,如果圖片非常大,就會(huì)造成 canvas 的 toDataURL 操作相當(dāng)?shù)暮臅r(shí),從而阻塞頁面的渲染。
所以解決思路即把這里的處理圖片的操作交由 worker 來處理。以下貼出主要的代碼:
Canvas to base64
以上是通過 canvas 來獲取圖片數(shù)據(jù),那么是否有其它方法呢?肯定有的啦,動(dòng)下腦筋吧少年。
本系列持續(xù)更新中,Github 地址請(qǐng)查閱這里。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/95131.html
摘要:異步編程通過在事件循環(huán)中調(diào)度要稍后執(zhí)行的部分代碼來使應(yīng)用程序能夠響應(yīng),從而允許首先執(zhí)行渲染。工作人員將通過定義的消息處理程序處理該消息。的應(yīng)用場景到目前為止,我們列出了的優(yōu)勢(shì)和局限性。 這一次我們將分拆Web Workers:我們將提供一個(gè)概述,討論不同類型的workers,他們的組成部分如何共同發(fā)揮作用,以及他們?cè)诓煌闆r下提供的優(yōu)勢(shì)和局限性。最后,我們將提供5個(gè)用例,其中Web W...
摘要:生命周期的生命周期和網(wǎng)頁完全不相關(guān)。意即會(huì)作用于整個(gè)源地址上。激活安裝完之后下一步即激活。同時(shí)檢查響應(yīng)類型是否為,即檢查請(qǐng)求是否同域。創(chuàng)建新的的過程將會(huì)啟動(dòng),然后觸發(fā)事件??梢岳媒俪志W(wǎng)絡(luò)連接和偽造響應(yīng)數(shù)據(jù)。 原文請(qǐng)查閱這里,略有刪減,本文采用知識(shí)共享署名 4.0 國際許可協(xié)議共享,BY Troland。 本系列持續(xù)更新中,Github 地址請(qǐng)查閱這里。 這是 JavaScript 工...
閱讀 620·2021-11-22 14:45
閱讀 3119·2021-10-15 09:41
閱讀 1639·2021-10-11 10:58
閱讀 2823·2021-09-04 16:45
閱讀 2645·2021-09-03 10:45
閱讀 3270·2019-08-30 15:53
閱讀 1250·2019-08-29 12:28
閱讀 2172·2019-08-29 12:14