溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

一文帶你看懂 Web Worker

發(fā)布時間:2020-08-08 22:47:41 來源:ITPUB博客 閱讀:157 作者:千鋒Python唐小強(qiáng) 欄目:編程語言

以前我們總說,JS是單線程沒有多線程,當(dāng)JS在頁面中運(yùn)行長耗時同步任務(wù)的時候就會導(dǎo)致頁面假死影響用戶體驗(yàn),從而需要設(shè)置把任務(wù)放在任務(wù)隊(duì)列中;執(zhí)行任務(wù)隊(duì)列中的任務(wù)也并非多線程進(jìn)行的,然而現(xiàn)在HTML5提供了我們前端開發(fā)這樣的能力 - Web Workers API,我們一起來看一看 Web Worker 是什么,怎么去使用它,在實(shí)際生產(chǎn)中如何去用它來進(jìn)行產(chǎn)出。

一文帶你看懂 Web Worker

1. 概述

Web Workers 使得一個Web應(yīng)用程序可以在與主執(zhí)行線程分離的后臺線程中運(yùn)行一個腳本操作。這樣做的好處是可以在一個單獨(dú)的線程中執(zhí)行費(fèi)時的處理任務(wù),從而允許主(通常是UI)線程運(yùn)行而不被阻塞。

它的作用就是給JS創(chuàng)造多線程運(yùn)行環(huán)境,允許主線程創(chuàng)建worker線程,分配任務(wù)給后者,主線程運(yùn)行的同時worker線程也在運(yùn)行,相互不干擾,在worker線程運(yùn)行結(jié)束后把結(jié)果返回給主線程。這樣做的好處是主線程可以把計(jì)算密集型或高延遲的任務(wù)交給worker線程執(zhí)行,這樣主線程就會變得輕松,不會被阻塞或拖慢。這并不意味著JS語言本身支持了多線程能力,而是瀏覽器作為宿主環(huán)境提供了JS一個多線程運(yùn)行的環(huán)境。

不過因?yàn)閣orker一旦新建,就會一直運(yùn)行,不會被主線程的活動打斷,這樣有利于隨時響應(yīng)主線程的通性,但是也會造成資源的浪費(fèi),所以不應(yīng)過度使用,用完注意關(guān)閉。或者說:如果worker無實(shí)例引用,該worker空閑后立即會被關(guān)閉;如果worker實(shí)列引用不為0,該worker空閑也不會被關(guān)閉。

看一看它的兼容性

一文帶你看懂 Web Worker

2. 使用

2.1 限制

worker線程的使用有一些注意點(diǎn)

同源限制

worker線程執(zhí)行的腳本文件必須和主線程的腳本文件同源,這是當(dāng)然的了,總不能允許worker線程到別人電腦上到處讀文件吧

文件限制

為了安全,worker線程無法讀取本地文件,它所加載的腳本必須來自網(wǎng)絡(luò),且需要與主線程的腳本同源

DOM操作限制

worker線程在與主線程的window不同的另一個全局上下文中運(yùn)行,其中無法讀取主線程所在網(wǎng)頁的DOM對象,也不能獲取 document、window等對象,但是可以獲取navigator、location(只讀)、XMLHttpRequest、setTimeout族等瀏覽器API。

通信限制

worker線程與主線程不在同一個上下文,不能直接通信,需要通過postMessage方法來通信。

腳本限制

worker線程不能執(zhí)行alert、confirm,但可以使用 XMLHttpRequest 對象發(fā)出ajax請求。

2.2 例子

在主線程中生成 Worker 線程很容易:

var myWorker = new Worker(jsUrl, options)

Worker()構(gòu)造函數(shù),第一個參數(shù)是腳本的網(wǎng)址(必須遵守同源政策),該參數(shù)是必需的,且只能加載 JS 腳本,否則報錯。第二個參數(shù)是配置對象,該對象可選。它的一個作用就是指定 Worker 的名稱,用來區(qū)分多個 Worker 線程。

// 主線程
var myWorker = new Worker('worker.js', { name : 'myWorker' });
// Worker 線程
self.name // myWorker

關(guān)于api什么的,直接上例子大概就能明白了,首先是worker線程的js文件:

// workerThread1.js
let i = 1
function simpleCount() {
 i++
 self.postMessage(i)
 setTimeout(simpleCount, 1000)
}
simpleCount()
self.onmessage = ev => {
 postMessage(ev.data + ' 呵呵~')
}

在HTML文件中的body中:

<!--主線程,HTML文件的body標(biāo)簽中-->
<div>
 Worker 輸出內(nèi)容:<span id='app'></span>
 <input type='text' title='' id='msg'>
 <button onclick='sendMessage()'>發(fā)送</button>
 <button onclick='stopWorker()'>stop!</button>
</div>
<script type='text/javascript'>
 if (typeof(Worker) === 'undefined')	// 使用Worker前檢查一下瀏覽器是否支持
 document.writeln(' Sorry! No Web Worker support.. ')
 else {
 window.w = new Worker('workerThread1.js')
 window.w.onmessage = ev => {
 document.getElementById('app').innerHTML = ev.data
 }
 
 window.w.onerror = err => {
 w.terminate()
 console.log(error.filename, error.lineno, error.message) // 發(fā)生錯誤的文件名、行號、錯誤內(nèi)容
 }
 
 function sendMessage() {
 const msg = document.getElementById('msg')
 window.w.postMessage(msg.value)
 }
 
 function stopWorker() {
 window.w.terminate()
 }
 }
</script>

可以自己運(yùn)行一下看看效果,上面用到了一些常用的 api

主線程中的api,worker表示是 Worker 的實(shí)例:

  • worker.postMessage: 主線程往worker線程發(fā)消息,消息可以是任意類型數(shù)據(jù),包括二進(jìn)制數(shù)據(jù)
  • worker.terminate: 主線程關(guān)閉worker線程
  • worker.onmessage: 指定worker線程發(fā)消息時的回調(diào),也可以通過worker.addEventListener('message',cb)的方式
  • worker.onerror: 指定worker線程發(fā)生錯誤時的回調(diào),也可以 worker.addEventListener('error',cb)

Worker線程中全局對象為 self,代表子線程自身,這時 this指向self,其上有一些api:

  • self.postMessage: worker線程往主線程發(fā)消息,消息可以是任意類型數(shù)據(jù),包括二進(jìn)制數(shù)據(jù)
  • self.close: worker線程關(guān)閉自己
  • self.onmessage: 指定主線程發(fā)worker線程消息時的回調(diào),也可以self.addEventListener('message',cb)
  • self.onerror: 指定worker線程發(fā)生錯誤時的回調(diào),也可以 self.addEventListener('error',cb)

【注意】w.postMessage(aMessage, transferList) 方法接受兩個參數(shù),aMessage 是可以傳遞任何類型數(shù)據(jù)的,包括對象,這種通信是拷貝關(guān)系,即是傳值而不是傳址,Worker 對通信內(nèi)容的修改,不會影響到主線程。事實(shí)上,瀏覽器內(nèi)部的運(yùn)行機(jī)制是,先將通信內(nèi)容串行化,然后把串行化后的字符串發(fā)給 Worker,后者再將它還原。一個可選的 Transferable對象的數(shù)組,用于傳遞所有權(quán)。如果一個對象的所有權(quán)被轉(zhuǎn)移,在發(fā)送它的上下文中將變?yōu)椴豢捎茫ㄖ兄梗?,并且只有在它被發(fā)送到的worker中可用??赊D(zhuǎn)移對象是如ArrayBuffer,MessagePort或ImageBitmap的實(shí)例對象,transferList數(shù)組中不可傳入null。

更詳細(xì)的API參見 MDN - WorkerGlobalScope。

worker線程中加載腳本的api:

importScripts('script1.js')	// 加載單個腳本
importScripts('script1.js', 'script2.js')	// 加載多個腳本

3. 實(shí)戰(zhàn)場景

個人覺得,Web Worker我們可以當(dāng)做計(jì)算器來用,需要用的時候掏出來摁一摁,不用的時候一定要收起來~

加密數(shù)據(jù)

有些加解密的算法比較復(fù)雜,或者在加解密很多數(shù)據(jù)的時候,這會非常耗費(fèi)計(jì)算資源,導(dǎo)致UI線程無響應(yīng),因此這是使用Web Worker的好時機(jī),使用Worker線程可以讓用戶更加無縫的操作UI。

預(yù)取數(shù)據(jù)

有時候?yàn)榱颂嵘龜?shù)據(jù)加載速度,可以提前使用Worker線程獲取數(shù)據(jù),因?yàn)閃orker線程是可以是用 XMLHttpRequest 的。

預(yù)渲染

在某些渲染場景下,比如渲染復(fù)雜的canvas的時候需要計(jì)算的效果比如反射、折射、光影、材料等,這些計(jì)算的邏輯可以使用Worker線程來執(zhí)行,也可以使用多個Worker線程,這里有個射線追蹤的示例。

復(fù)雜數(shù)據(jù)處理場景

某些檢索、排序、過濾、分析會非常耗費(fèi)時間,這時可以使用Web Worker來進(jìn)行,不占用主線程。

預(yù)加載圖片

有時候一個頁面有很多圖片,或者有幾個很大的圖片的時候,如果業(yè)務(wù)限制不考慮懶加載,也可以使用Web Worker來加載圖片,可以參考一下這篇文章的探索,這里簡單提要一下。

// 主線程
let w = new Worker("js/workers.js");
w.onmessage = function (event) {
 var img = document.createElement("img");
 img.src = window.URL.createObjectURL(event.data);
 document.querySelector('#result').appendChild(img)
}
// worker線程
let arr = [...好多圖片路徑];
for (let i = 0, len = arr.length; i < len; i++) {
 let req = new XMLHttpRequest();
 req.open('GET', arr[i], true);
 req.responseType = "blob";
 req.setRequestHeader("client_type", "DESKTOP_WEB");
 req.onreadystatechange = () => {
 if (req.readyState == 4) {
 postMessage(req.response);
 }
 }
 req.send(null);
}

在實(shí)戰(zhàn)的時候注意:

雖然使用worker線程不會占用主線程,但是啟動worker會比較耗費(fèi)資源

主線程中使用XMLHttpRequest在請求過程中瀏覽器另開了一個異步http請求線程,但是交互過程中還是要消耗主線程資源

至于還有Shared Worker、Service Worker什么的,我們就不看了,IE不喜歡

好了,這篇文章就先分享到這,有不清楚的伙伴可以留言哈!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI