-
介紹
web worker HTML5提供得,運(yùn)行在后臺(tái)的 JavaScript,獨(dú)立于其他腳本,不會(huì)影響頁面的性能。您可以繼續(xù)做任何愿意做的事情:點(diǎn)擊、選取內(nèi)容等等,而此時(shí) web worker 在后臺(tái)運(yùn)行。
-
作用
給JS創(chuàng)造多線程運(yùn)行環(huán)境,允許主線程創(chuàng)建worker線程,分配任務(wù)給后者,主線程運(yùn)行的同時(shí)worker線程也在運(yùn)行,相互不干擾,在worker線程運(yùn)行結(jié)束后把結(jié)果返回給主線程。
這樣做的好處是主線程可以把計(jì)算密集型或高延遲的任務(wù)交給worker線程執(zhí)行,這樣主線程就會(huì)變得輕松,不會(huì)被阻塞或拖慢。
這并不意味著JS語言本身支持了多線程能力,而是瀏覽器作為宿主環(huán)境提供了JS一個(gè)多線程運(yùn)行的環(huán)境。
不過因?yàn)閣orker一旦新建,就會(huì)一直運(yùn)行,不會(huì)被主線程的活動(dòng)打斷,這樣有利于隨時(shí)響應(yīng)主線程的通性,但是也會(huì)造成資源的浪費(fèi),所以不應(yīng)過度使用,用完注意關(guān)閉。
-
兼容性
Browser IE Edge FireFox Chrome Safari version 10+ 12+ 3.5+ 4+ 4+ -
使用
-
注意事項(xiàng):
- 同源限制:分配給 Worker 線程運(yùn)行的腳本文件,必須與主線程的腳本文件同源。
- DOM操作限制:Worker 線程所在的全局對(duì)象,與主線程不一樣,無法讀取主線程所在網(wǎng)頁的 DOM 對(duì)象。也不能獲取
document、window等對(duì)象,但是可以獲取navigator、location(只讀)、XMLHttpRequest、setTimeout族等瀏覽器API。 - 通信限制:Worker 線程和主線程不在同一個(gè)上下文環(huán)境,它們不能直接通信,必須通過消息完成。
- 腳本限制:Worker 線程不能執(zhí)行
alert()方法和confirm()方法,但可以使用 XMLHttpRequest 對(duì)象發(fā)出 AJAX 請(qǐng)求。 - 文件限制:Worker 線程無法讀取本地文件,即不能打開本機(jī)的文件系統(tǒng)(
file://),它所加載的腳本,必須來自網(wǎng)絡(luò)。
-
例子:
-
主線程
- 創(chuàng)建 Worker 線程:
// 兩個(gè)參數(shù) var myWorker = new Worker(jsUrl, options);Worker()構(gòu)造函數(shù),第一個(gè)參數(shù)是腳本的網(wǎng)址(必須遵守同源政策),該參數(shù)是必需的,且只能加載 JS 腳本,否則報(bào)錯(cuò)。第二個(gè)參數(shù)是配置對(duì)象,該對(duì)象可選。它的一個(gè)作用就是指定 Worker 的名稱,用來區(qū)分多個(gè) Worker 線程。
- 主線程代碼示例:
<!--主線程,HTML文件的body標(biāo)簽中--> <div> myworker 輸出內(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'> // 使用Worker前檢查一下瀏覽器是否支持 if ( typeof ( Worker ) === 'undefined') document.writeln(' Sorry! No Web Worker support.. ') else { // 創(chuàng)建 子線程 worker let worker = new Worker('webWork.js', { name : 'worker' }); // 主線程 給 子線程 發(fā)消息 worker.postMessage('Hello World'); // worker.postMessage({method: 'echo', args: ['Work']}); // 主線程 監(jiān)聽 子線程 發(fā)的消息 worker.onmessage = function (event) { console.log('Received message ' + event.data); doSomething(); } function doSomething() { // 執(zhí)行任務(wù) 通知 子線程調(diào)用結(jié)束 worker.postMessage('Work done!'); // 使用 完畢 關(guān)閉 子線程 worker.terminate(); } // 子線程 myworker let myworker = new Worker('webWork2.js', { name : 'myworker' }); // myworker 進(jìn)行計(jì)算 監(jiān)聽返回的結(jié)果 顯示在頁面上 myworker.onmessage = (e) => { document.getElementById("app").innerHTML = e.data } // 點(diǎn)擊停止 按鈕 通知 mywork 停止計(jì)算 function stopWorker () { myworker.postMessage('stop'); // myworker 完成任務(wù)以后,主線程就可以把它關(guān)掉。 myworker.terminate(); } } </script> -
Worker線程
- Worker線程代碼示例:
// webWork.js console.log(self.name,'webWork.js'); // self代表子線程自身,即子線程的全局對(duì)象。 // // 可以自己寫監(jiān)聽 // self.addEventListener('message', function (e) { // console.log(e.data,'data') // self.postMessage('You said: ' + e.data); // }, false); // 也可以使用 onmessage 來進(jìn)行監(jiān)聽 self.onmessage = (e) => { console.log(e.data,'e') self.postMessage('You said: ' + e.data); if (e.data == 'Work done!') { self.close(); } }// webWork2.js console.log(self.name,'webWork2.js'); // 定時(shí)器 計(jì)算值 返回給 主線程 let i = 1; function add () { i ++; self.postMessage(i); setTimeout(add, 1000); } add(); // 監(jiān)聽主線程消息 self.onmessage = (e) => { if (e.data == 'stop') { self.close(); } } -
Worker 加載腳本
- Worker 內(nèi)部如果要加載其他腳本,有一個(gè)專門的方
importScripts()。
// 引用單個(gè)腳本 importScripts('script1.js'); // 引用多個(gè)腳本 importScripts('script1.js', 'script2.js'); - Worker 內(nèi)部如果要加載其他腳本,有一個(gè)專門的方
-
錯(cuò)誤處理
- 主線程可以監(jiān)聽 Worker 是否發(fā)生錯(cuò)誤。如果發(fā)生錯(cuò)誤,Worker 會(huì)觸發(fā)主線程的
error事件。worker.onerror(function (event) { console.log([ 'ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message ].join('')); }); // 或者 worker.addEventListener('error', function (event) { // ... });
Worker 內(nèi)部也可以監(jiān)聽
error事件。 - 主線程可以監(jiān)聽 Worker 是否發(fā)生錯(cuò)誤。如果發(fā)生錯(cuò)誤,Worker 會(huì)觸發(fā)主線程的
-
關(guān)閉 Worker
-
使用完畢,為了節(jié)省系統(tǒng)資源,必須關(guān)閉 Worker。
// 主線程 worker.terminate(); // Worker 線程 self.close();
-
-
-
-
數(shù)據(jù)通信
-
主線程與 Worker 之間的通信內(nèi)容,可以是文本,也可以是對(duì)象。需要注意的是,這種通信是拷貝關(guān)系,即是傳值而不是傳址,Worker 對(duì)通信內(nèi)容的修改,不會(huì)影響到主線程。事實(shí)上,瀏覽器內(nèi)部的運(yùn)行機(jī)制是,先將通信內(nèi)容串行化,然后把串行化后的字符串發(fā)給 Worker,后者再將它還原。
主線程與 Worker 之間也可以交換二進(jìn)制數(shù)據(jù),比如 File、Blob、ArrayBuffer 等類型,也可以在線程之間發(fā)送。但是,拷貝方式發(fā)送二進(jìn)制數(shù)據(jù),會(huì)造成性能問題。比如,主線程向 Worker 發(fā)送一個(gè) 500MB 文件,默認(rèn)情況下瀏覽器會(huì)生成一個(gè)原文件的拷貝。為了解決這個(gè)問題,JavaScript 允許主線程把二進(jìn)制數(shù)據(jù)直接轉(zhuǎn)移給子線程,但是一旦轉(zhuǎn)移,主線程就無法再使用這些二進(jìn)制數(shù)據(jù)了,這是為了防止出現(xiàn)多個(gè)線程同時(shí)修改數(shù)據(jù)的麻煩局面。這種轉(zhuǎn)移數(shù)據(jù)的方法,叫做Transferable Objects。這使得主線程可以快速把數(shù)據(jù)交給 Worker,對(duì)于影像處理、聲音處理、3D 運(yùn)算等就非常方便了,不會(huì)產(chǎn)生性能負(fù)擔(dān)。
-
如果要直接轉(zhuǎn)移數(shù)據(jù)的控制權(quán),就要使用下面的寫法。
// Transferable Objects 格式 worker.postMessage(arrayBuffer, [arrayBuffer]); // 例子 var ab = new ArrayBuffer(1); worker.postMessage(ab, [ab]);
-
-
-
常用的API
-
主線程中的API:
-
worker.postMessage: 主線程往worker線程發(fā)消息,消息可以是任意類型數(shù)據(jù),包括二進(jìn)制數(shù)據(jù) -
worker.terminate: 主線程關(guān)閉worker線程 -
worker.onmessage: 指定worker線程發(fā)消息時(shí)的回調(diào),也可以通過worker.addEventListener('message',cb)的方式 -
worker.onerror: 指定worker線程發(fā)生錯(cuò)誤時(shí)的回調(diào),也可以worker.addEventListener('error',cb)
-
-
Worker線程中的API:
-
self.postMessage: worker線程往主線程發(fā)消息,消息可以是任意類型數(shù)據(jù),包括二進(jìn)制數(shù)據(jù) -
self.close: worker線程關(guān)閉自己 -
self.onmessage: 指定主線程發(fā)worker線程消息時(shí)的回調(diào),也可以self.addEventListener('message',cb) -
self.onerror: 指定worker線程發(fā)生錯(cuò)誤時(shí)的回調(diào),也可以self.addEventListener('error',cb)
-
-
主線程中的API:
-
實(shí)戰(zhàn)場景
-
輪詢
有時(shí),瀏覽器需要輪詢服務(wù)器狀態(tài),以便第一時(shí)間得知狀態(tài)改變。這個(gè)工作可以放在 Worker 里面。
function createWorker(f) { var blob = new Blob(['(' + f.toString() +')()']); var url = window.URL.createObjectURL(blob); var worker = new Worker(url); return worker; } var pollingWorker = createWorker(function (e) { var cache; function compare(new, old) { ... }; setInterval(function () { fetch('/my-api-endpoint').then(function (res) { var data = res.json(); if (!compare(data, cache)) { cache = data; self.postMessage(data); } }) }, 1000) }); pollingWorker.onmessage = function () { // render data } pollingWorker.postMessage('init');
-
-
加密數(shù)據(jù)
有些加解密的算法比較復(fù)雜,或者在加解密很多數(shù)據(jù)的時(shí)候,這會(huì)非常耗費(fèi)計(jì)算資源,導(dǎo)致UI線程無響應(yīng),因此這是使用Web Worker的好時(shí)機(jī),使用Worker線程可以讓用戶更加無縫的操作UI。
-
預(yù)取數(shù)據(jù)
有時(shí)候?yàn)榱颂嵘龜?shù)據(jù)加載速度,可以提前使用Worker線程獲取數(shù)據(jù),因?yàn)閃orker線程是可以是用
XMLHttpRequest的。 -
預(yù)渲染
在某些渲染場景下,比如渲染復(fù)雜的canvas的時(shí)候需要計(jì)算的效果比如反射、折射、光影、材料等,這些計(jì)算的邏輯可以使用Worker線程來執(zhí)行,也可以使用多個(gè)Worker線程。
-
復(fù)雜數(shù)據(jù)處理場景
某些檢索、排序、過濾、分析會(huì)非常耗費(fèi)時(shí)間,這時(shí)可以使用Web Worker來進(jìn)行,不占用主線程。
-
預(yù)加載圖片
有時(shí)候一個(gè)頁面有很多圖片,或者有幾個(gè)很大的圖片的時(shí)候,如果業(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); }
-
注意事項(xiàng)
雖然使用worker線程不會(huì)占用主線程,但是啟動(dòng)worker會(huì)比較耗費(fèi)資源
主線程中使用XMLHttpRequest在請(qǐng)求過程中瀏覽器另開了一個(gè)異步http請(qǐng)求線程,但是交互過程中還是要消耗主線程資源
-
參考鏈接