web worker介紹以及應用場景
- 什麼是web worker ?
- 爲什麼要用web worker ?
- 怎麼使用web worker ?
- 1.主線程採用new 命令,調用worker構造函數,Worker中接收兩個參數,一個url,就是worker執行計算的腳本的路徑url(爲必須參數),worker線程所要執行的任務,這個腳本必須來自網絡,放到服務器上,如果腳本沒下載成功,就會失敗,第二個參數是配置對象,該對象可選。它的一個作用就是指定 Worker 的名稱,用來區分多個 Worker 線程;
- 2.javascript主線程中和worker子線程中通過postMessage和onmessage來通信,由主線程中postMessage(params),傳入參數,子線程通過onmessage函數接收從主線程傳遞過來的參數,通過獲取參數中的data來取值。
- 3.worker中加載其他的腳本,有一個專門的方法importScripts()。
- 4.監聽錯誤
- 實例代碼
- web worker 注意事項:
- web worker的應用場景有哪些?
面試遇到一個問題,就是如果有幾十萬條數據,前端該怎麼處理?
我回答用分頁,懶加載去加載數據,但是面試官說後臺不給處理,前端該怎麼處理?
如果都渲染到頁面上來,瀏覽器根本就承擔不了,這個問題真的沒回答上來,
查了一下就查到了web worker,新的知識點,當然要知道what,why,how了。
什麼是web worker ?
web worker 是Web應用程序在獨立於主線程的後臺線程中,運行一個腳本的操作。使用構造函數(例如,Worker())創建一個 worker 對象, 構造函數接受一個 JavaScript文件URL , 這個文件包含了將在 worker 線程中運行的代碼腳本。
主線程和 worker 線程相互之間使用 postMessage() 方法來發送信息, 並且通過 onmessage這個 event handler來接收信息(傳遞的信息包含在 Message 這個事件的data屬性內) 。數據的交互方式爲傳遞副本,而不是直接共享數據。
worker 將運行在與當前 window不同的另一個全局上下文中,這個上下文由一個對象表示,標準情況下爲DedicatedWorkerGlobalScope
爲什麼要用web worker ?
在瀏覽器中javascript的執行是單線程的,一個線程一次只能做一件事情,這樣必然會出現阻塞的情況,非常影響用戶體驗,所以ajax應運而生了。ajax的出現使得頁面在等待服務器響應的這段時間內不再發生阻塞,但是這仍然沒有改變代碼單線程執行的本質,這也意味着我們依舊不能把耗費時間的複雜運算放在頁面上執行。而web worker的出現彌補了這個缺點。
web worker爲 JavaScript 創造多線程環境,允許主線程通過new運算符創建 Worker 線程(子線程),將一些任務分配給Worker線程,這樣一些計算量大的、耗費時間的、複雜的運算都可以放到worker線程上,避免主線程被阻塞。
怎麼使用web worker ?
1.主線程採用new 命令,調用worker構造函數,Worker中接收兩個參數,一個url,就是worker執行計算的腳本的路徑url(爲必須參數),worker線程所要執行的任務,這個腳本必須來自網絡,放到服務器上,如果腳本沒下載成功,就會失敗,第二個參數是配置對象,該對象可選。它的一個作用就是指定 Worker 的名稱,用來區分多個 Worker 線程;
2.javascript主線程中和worker子線程中通過postMessage和onmessage來通信,由主線程中postMessage(params),傳入參數,子線程通過onmessage函數接收從主線程傳遞過來的參數,通過獲取參數中的data來取值。
myworker.postMessage()方法的參數,就是主線程傳給 Worker 的數據。它可以是各種數據類型,包括二進制數據。
//主線程
let myworker = new Worker('worker.js');
this.myWorker.postMessage([value1, value2])
子線程接收到主線程傳遞過來的條件參數,將經過處理的數據通過postMessage(Data)傳遞給主線程,
//子線程
onmessage = function(e) {
console.log(e.data, 'Worker: Message received from main script')
const result = e.data[0] * e.data[1]
if (isNaN(result)) {
postMessage('Please write two numbers')
} else {
const workerResult = 'Result: ' + result
console.log('Worker: Posting message back to main script')
postMessage(workerResult)
}
}
接着,主線程通過myworker.onmessage指定監聽函數,這樣主線程就接收到子線程發回來的消息了
//主線程
this.myWorker.onmessage = function (e) {
result.textContent = e.data
console.log('Message received from worker')
}
主線程接收到經過子線程處理過的數據之後可以關閉子線程:
this.myworker.terminate()
worker線程中有一個監聽函數,監聽message事件,以下寫法二和寫法三中self和this都可以,worker 將運行在與當前 window不同的另一個全局上下文中,這個上下文由一個對象表示,標準情況下爲DedicatedWorkerGlobalScope,self和this都指向這個對象,(標準 workers 由單個腳本使用; 共享workers使用SharedWorkerGlobalScope)
寫法一:
onmessage = function(e) {
postMessage('You said: ' + e.data);
}
寫法二:
self.addEventListener('message', function(e) {
postMessage('You said: ' + e.data);
})
寫法三:
this.addEventListener('message', function(e) {
postMessage('You said: ' + e.data);
})
寫法四:
addEventListener('message', function (e) {
postMessage('You said: ' + e.data);
}, false);
根據主線程發來的數據,Worker 線程可以調用不同的方法,下面是一個例子。
self.addEventListener('message', function (e) {
var data = e.data;
switch (data.cmd) {
case 'start':
self.postMessage('WORKER STARTED: ' + data.msg);
break;
case 'stop':
self.postMessage('WORKER STOPPED: ' + data.msg);
self.close(); // Terminates the worker.
break;
default:
self.postMessage('Unknown command: ' + data.msg);
};
}, false);
上面代碼中,self.close()用於在 Worker 內部關閉自身。
3.worker中加載其他的腳本,有一個專門的方法importScripts()。
importScripts('script1.js');
可同時加載多個腳本
importScripts('script1.js', 'script2.js');
4.監聽錯誤
主線程可以監聽 Worker 是否發生錯誤。如果發生錯誤,Worker 會觸發主線程的error事件。
worker.onerror(function (event) {
console.log([
'ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message
].join(''));
});
// 或者
worker.addEventListener('error', function (event) {
// ...
});
Worker 內部也可以監聽error事件。
2.5 關閉 Worker
使用完畢,爲了節省系統資源,必須關閉 Worker。
// 主線程
worker.terminate();
// Worker 線程
self.close();
實例代碼
以下例子在vue項目中使用如下:
webWorker.vue
<template>
<div tabindex="0">
<h1>Web Workers</h1>
<form>
<div>
<label for="number1">number 1: </label>
<input type="text" id="number1" :value="inputValue1" @change="handleChange1">
</div>
<div>
<label for="number2">number 2: </label>
<input type="text" id="number2" :value="inputValue2" @change="handleChange2">
</div>
</form>
<p class="result">Result: 0</p>
</div>
</template>
<script>
export default {
data () {
return {
inputValue1: 0,
inputValue2: 0
}
},
methods: {
workerFun (value1, value2, type) {
const result = document.querySelector('.result')
if (window.Worker) {
this.myWorker = new Worker('worker.jsx')
this.myWorker.postMessage([value1, value2])
console.log('Message posted to worker', type)
this.myWorker.onmessage = function (e) {
result.textContent = e.data
this.myworker.terminate()
console.log('Message received from worker')
}
} else {
console.log('Your browser doesn\'t support web workers.')
}
},
handleChange1 (e) {
this.inputValue1 = e.target.value
this.workerFun(e.target.value, this.inputValue2, 'first')
},
handleChange2 (e) {
this.inputValue2 = e.target.value
this.workerFun(this.inputValue1, e.target.value, 'second')
}
}
}
</script>
worker.jsx放到根目錄下面,代碼如下:
onmessage = function(e) {
console.log(e.data, 'Worker: Message received from main script')
const result = e.data[0] * e.data[1]
if (isNaN(result)) {
postMessage('Please write two numbers')
} else {
const workerResult = 'Result: ' + result
console.log('Worker: Posting message back to main script')
postMessage(workerResult)
}
}
web worker 注意事項:
(1)同源限制
分配給 Worker 線程運行的腳本文件,必須與主線程的腳本文件同源。
(2)DOM 限制
Worker 線程所在的全局對象,與主線程不一樣,無法讀取主線程所在網頁的 DOM 對象,也無法使用document、window、parent這些對象。但是,Worker 線程可以navigator對象和location對象。
(3)通信聯繫
Worker 線程和主線程不在同一個上下文環境,它們不能直接通信,必須通過消息完成。
(4)腳本限制
Worker 線程不能執行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 對象發出 AJAX 請求。
(5)文件限制
Worker 線程無法讀取本地文件,即不能打開本機的文件系統(file://),它所加載的腳本,必須來自網絡。
web worker的應用場景有哪些?
一般情況下,很少會用到web Worker,返回到瀏覽器到數據在後臺已經經過計算處理,不太可能讓前端處理複雜到業務邏輯
webWorker解決的是js中數據處理導致的UI線程阻塞,所以webWorker的應用場景一般爲需要大量計算的時候,這將避免ui界面渲染被阻塞
大數據的處理:
這裏所說的大數據處理,並不是指數據量非常大,而是要從計算量來看,通常用時不能控制在毫秒級內的運算都可以考慮放在web worker中執行。
高頻的用戶交互:
高頻的用戶交互適用於根據用戶的輸入習慣、歷史記錄以及緩存等信息來協助用戶完成輸入的糾錯、校正功能等類似場景,用戶頻繁輸入的響應處理同樣可以考慮放在web worker中執行。
最後聲明一點,瞭解後臺的同學千萬不要認爲web worker等同於後臺意義的多線程,web worker現在有了多線程的形(有了多線程的用法),但還不具備多線程的神(不具備線程通信、鎖等後臺線程的基本特性),web worker的本質是支持我們把數據刷新與頁面渲染兩個動作拆開執行(不使用web worker的話這兩個動作在主線程中是線性執行的)。
爲了防止主線程造成擁堵,可以開啓worker線程,這樣不影響主線程的數據加載,可以在worker中進行數據請求,也可以創建多個worker線程,不過Web worker也有一些使用限制:無法訪問DOM節點,無法訪問全局變量或是全局函數。所以之前用的是jQuery Ajax發送請求的,這裏就不能用了;只能用原生的ajax請求啦。
self.addEventListener('message', function(e) {
const result = e.data[0] * e.data[1]
if (isNaN(result)) {
postMessage('Please write two numbers')
} else {
var xhr = new XMLHttpRequest()
let ajaxResult
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
ajaxResult = JSON.parse(xhr.responseText)
const workerResult = 'Result: ' + result
postMessage({
workerResult,
ajaxResult: ajaxResult.result
})
}
}
}
xhr.open('get', 'https://geo.datav.aliyun.com/areas_v2/bound/100000_full.json', true)
xhr.send(null)
}
})
如有錯誤請指出,謝謝。
參考:
http://www.ruanyifeng.com/blog/2018/07/web-worker.html
https://blog.csdn.net/lqlqlq007/article/details/79824122