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)
})
結語
點贊支持、手留余香、與有榮焉