如何使用 Web Worker 為 JS 創(chuàng)造多線程環(huán)境?

Web Worker是什么

我們都知道JS是單線程的,所有任務在一個線程上,一次只能做一件事。雖然可以通過AJAX、定時器等可以實現(xiàn)"并行",但還是沒有改變JS單線程的本質(zhì),把一些復雜的運算放在頁面上執(zhí)行,還是會導致很卡,甚至卡死

而HTML5標準中的Web Worker為JS創(chuàng)造多線程環(huán)境,允許主線程創(chuàng)建Worker線程并給它分配任務,而且在主線程執(zhí)行任務的時候,worker線程可以同時在后臺執(zhí)行它的任務,互不干擾

這讓我們可以將一些復雜運算、高頻輸入的響應處理、大文件分片上傳等放在worker線程處理,最后再返回給主線程。很大程度上緩解了主線程UI渲染阻塞的問題,頁面就會很流暢

使用 Web Worker 有幾個注意點:

  • 同源限制:worker線程運行的文件必須和主線程的腳本是同源
  • 文件限制:worker線程不能打開本機的文件系統(tǒng)(file://),只能加載網(wǎng)絡文件
  • 其他限制:worker線程不能操作DOM、不能使用document、window、parent對象和alert、confirm方法

但可以使用location、navigator,不過只能只讀不能改寫;還可以使用XMLHttpRequest發(fā)送AJAX請求;還可以使用緩存

怎么使用 Web Worker呢?

分別看看在主線程的頁面和 worker 線程中的方法和用法

主線程中的方法和使用

創(chuàng)建 worker 進程,直接 new 就完事兒了,而且主線程內(nèi)和 worker 線程內(nèi)都可以創(chuàng)建多個 worker 線程

可以傳兩個參數(shù),第一個是網(wǎng)絡腳本文件的鏈接,不能是本地路徑,第二個是配置對象,不是必填

假設引入了一個網(wǎng)絡文件 worker1.js

// 主線程
const worker = new Worker('http://worker1.js')

然后使用 worker.postMessage() 方法向 worker 線程發(fā)送數(shù)據(jù),參數(shù)可以是各種數(shù)據(jù)類型,包括二進制。注意!發(fā)送過去的數(shù)據(jù)是傳值,也就是拷貝關系而不是傳址,所以 worker 線程內(nèi)部無論怎么修改,都不會影響到主線程的數(shù)據(jù)

worker.postMessage('這是發(fā)給worker線程的消息')

然后再通過 worker.onmessage 監(jiān)聽并接收 worker 線程處理完返回的數(shù)據(jù)

worker.onmessage = function(event){
    const data = event.data // 這是返回的數(shù)據(jù)
}

如果 worker 線程中內(nèi)部發(fā)生錯誤,會觸發(fā)主線程的 onerror 事件

worker.onerror( event => {
    console.log( 'worker 線程內(nèi)部執(zhí)行報錯了', event )
})
// 或者
worker.onerrer = function( event ){
    console.log( 'worker 線程內(nèi)部執(zhí)行報錯了', event )
}
// 或者
worker.addEventListener('error', event => {
    console.log( 'worker 線程內(nèi)部執(zhí)行報錯了', event )
})

最后是關掉 worker 線程,因為 worker 線程創(chuàng)建成功就會始終運行,不會主動關閉,所以我們不用的時候要主動關掉,避免浪費資源

worker.terminate() // 這樣 worker 線程就關閉了

worker 線程中的方法和使用

worker 線程內(nèi)部執(zhí)行上下文跟主線程的執(zhí)行上下文 window 不一樣,是一個叫 WorkerGlobalScope 的東西,我們可以用它或者 self 來訪問全局對象

如果主線程創(chuàng)建 worker 線程的時候有傳第二個參數(shù),在 worker1.js 里面,也就是在 worker 線程里可以直接這樣獲取,比如區(qū)分多個 worker 線程的時候

// 主線程
const worker = new Worker('http://worker1.js', { name: 'worker1' })

// worker1.js
self.name // worker1  

worker 線程里面,需要監(jiān)聽并接收主線程發(fā)送過來的數(shù)據(jù)。

self.addEventListener('message', event => {
    const data = event.data // 接收到的數(shù)據(jù) 
    ...
    self.postMessage( '這是處理好的數(shù)據(jù)' ) // 將處理好的數(shù)據(jù)發(fā)送給主線程
}, false)
// 或者 不要 self 也可以
addEventListener('message', event => {
    const data = event.data // 接收到的數(shù)據(jù) 
    ...
    self.postMessage( '這是處理好的數(shù)據(jù)' ) // 將處理好的數(shù)據(jù)發(fā)送給主線程
}, false)

如果 worker 線程內(nèi)部需要加載其他腳本,只能用importScripts()方法,路徑還是只能網(wǎng)絡文件,不能使用本地的

importScripts('http://worker2.js', 'http://worker3.js') // 可以引入多個

worker 線程內(nèi)部也可以使用 onerror 監(jiān)聽錯誤,和主線程使用方式一樣。

worker 線程內(nèi)部也可以關閉 worker 線程,方法和主線程不一樣

self.close() // 這樣就從內(nèi)部關閉了

worker 線程能不能直接寫在主線程的頁面里

能!

既然它要接收一個網(wǎng)絡地址URL,就給它創(chuàng)建一個URL

首先給 script 標簽起一個瀏覽器不認識的type,然后將這個標簽里的內(nèi)容轉成二進制對象,然后為這個對象生成URL,再用這個URL創(chuàng)建 worker 線程,如下

<!DOCTYPE html>
    <body>
        <div>這是頁面</div>
        <script id="worker" type="xxxxx">
            // worker 線程
            addEventListener('message', event => {
                console.log(event.data) // 收到了嗎
                postMessage('收到了') // 發(fā)送給主線程
            })
        </script>
        <script type="text/javascript">
            // 主線程
            const blob = new Blob([ document.querySelector('#worker').textContent ])
            const url = window.URL.createObjectURL(blob)
            const worker = new Worker(url)
            worker.postMessage('收到了嗎')
            worker.onmessage = event => {
                console.log(event.data) // 收到了
            }
        </script>
    </body>
</html>

worker 線程輪詢

function createWorker(fn){
    const blob = new Blob([fn.toString()])
    const url = window.URL.createObjectURL(blob)
    const worker = new Worker(url)
    return worker
}

const webWorker = createWorker(function(){
    let cache;
    
    function compare(new, old){ ... }
    
    setInterval(()=>{
        fetch('/api/xxx').then(res=>{
            let data = res.data
            if(!compare(data, cache)){
                cache = data
                self.postMessage(data)
            }
        })
    },1000)
})

結語

點贊支持、手留余香、與有榮焉

參考

# Web Worker 使用教程

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容