Web Worker是什么
我們都知道JS是單線程的,所有任務(wù)在一個線程上襟衰,一次只能做一件事贴铜。雖然可以通過AJAX、定時器等可以實(shí)現(xiàn)"并行"瀑晒,但還是沒有改變JS單線程的本質(zhì)绍坝,把一些復(fù)雜的運(yùn)算放在頁面上執(zhí)行,還是會導(dǎo)致很卡苔悦,甚至卡死
而HTML5標(biāo)準(zhǔn)中的Web Worker為JS創(chuàng)造多線程環(huán)境轩褐,允許主線程創(chuàng)建Worker線程并給它分配任務(wù),而且在主線程執(zhí)行任務(wù)的時候玖详,worker線程可以同時在后臺執(zhí)行它的任務(wù)灾挨,互不干擾
這讓我們可以將一些復(fù)雜運(yùn)算、高頻輸入的響應(yīng)處理竹宋、大文件分片上傳等放在worker線程處理,最后再返回給主線程地技。很大程度上緩解了主線程UI渲染阻塞的問題蜈七,頁面就會很流暢
使用 Web Worker 有幾個注意點(diǎn):
- 同源限制:worker線程運(yùn)行的文件必須和主線程的腳本是同源的
- 文件限制:worker線程不能打開本機(jī)的文件系統(tǒng)(file://),只能加載網(wǎng)絡(luò)文件
- 其他限制:worker線程不能操作DOM莫矗、不能使用document飒硅、window、parent對象和alert作谚、confirm方法
但可以使用location三娩、navigator,不過只能只讀不能改寫妹懒;還可以使用XMLHttpRequest發(fā)送AJAX請求雀监;還可以使用緩存
怎么使用 Web Worker呢?
分別看看在主線程的頁面和 worker 線程中的方法和用法
主線程中的方法和使用
創(chuàng)建 worker 進(jìn)程眨唬,直接 new 就完事兒了会前,而且主線程內(nèi)和 worker 線程內(nèi)都可以創(chuàng)建多個 worker 線程
可以傳兩個參數(shù),第一個是網(wǎng)絡(luò)腳本文件的鏈接匾竿,不能是本地路徑瓦宜,第二個是配置對象,不是必填
假設(shè)引入了一個網(wǎng)絡(luò)文件 worker1.js
// 主線程
const worker = new Worker('http://worker1.js')
然后使用 worker.postMessage()
方法向 worker 線程發(fā)送數(shù)據(jù)岭妖,參數(shù)可以是各種數(shù)據(jù)類型临庇,包括二進(jìn)制反璃。注意!發(fā)送過去的數(shù)據(jù)是傳值假夺,也就是拷貝關(guān)系而不是傳址淮蜈,所以 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 )
})
最后是關(guān)掉 worker 線程礁芦,因?yàn)?worker 線程創(chuàng)建成功就會始終運(yùn)行,不會主動關(guān)閉悼尾,所以我們不用的時候要主動關(guān)掉柿扣,避免浪費(fèi)資源
worker.terminate() // 這樣 worker 線程就關(guān)閉了
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)絡(luò)文件,不能使用本地的
importScripts('http://worker2.js', 'http://worker3.js') // 可以引入多個
worker 線程內(nèi)部也可以使用 onerror 監(jiān)聽錯誤娩怎,和主線程使用方式一樣搔课。
worker 線程內(nèi)部也可以關(guān)閉 worker 線程,方法和主線程不一樣
self.close() // 這樣就從內(nèi)部關(guān)閉了
worker 線程能不能直接寫在主線程的頁面里
能截亦!
既然它要接收一個網(wǎng)絡(luò)地址URL爬泥,就給它創(chuàng)建一個URL
首先給 script 標(biāo)簽起一個瀏覽器不認(rèn)識的type,然后將這個標(biāo)簽里的內(nèi)容轉(zhuǎn)成二進(jìn)制對象崩瓤,然后為這個對象生成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)
})
結(jié)語
點(diǎn)贊支持却桶、手留余香境输、與有榮焉