HTML5的時代來到了,由于前端負責(zé)的數(shù)據(jù)處理越來越大,單單靠一個JS的單線程越來越力不從心,webWorker的出現(xiàn)能更好的解決該問題,這篇文章將講述webWorker的使用。
簡單的實例
使用webWorker我們可以將一些大量計算的事情放置在另一個線程進行處理,這時候我們頁面不會被卡死,讓網(wǎng)頁使用流暢。
比如我們進行一個50億次的加法計算。
console.time('啟動頁面');
function add5billion(){
var x=0;
for(var i=0;i<1000000000;i++){
x++;
x++;
x++;
x++;
x++;
}
return x;
}
add5billion();
console.timeEnd('啟動頁面');//啟動頁面: 2884.2ms

在我的電腦上是2秒多,但一般的電腦可能要慢很多,但是作為頁面展示如果需要2秒的時間那么用戶體驗將是相當(dāng)不好的,因為兩秒種的時間用戶都無法操作。
這時候一般只能使用 setTimeout 將計算拆除,或者請求服務(wù)器處理,但是都很麻煩,在這里我們使用 Worker 就可以了。
script of html
console.time('啟動頁面');
var worker=new Worker('worker-5x10^9.js');//50億加法計算的函數(shù)文件
worker.onmessage=function(e){
console.log(e.data);
}
console.timeEnd('啟動頁面');//啟動頁面: 0.196ms
worker-5x10^9.js
var x=0;
for(var i=0;i<1000000000;i++){
x++;
x++;
x++;
x++;
x++;
}
this.postMessage(x);


使用webWorker可以避免大量的計算導(dǎo)致的擁塞,避免頁面假死,充分利用客戶端的物理資源。
下面我們來詳細講述如何使用 webWorker。
Worker簡介
兼容性
雖然作為HTM5的新特性,但是各大瀏覽器對 webWorker 的支持都是很好的,所以我們基本可以大膽的嘗試在工程中使用。

構(gòu)造函數(shù)
webWorker 在瀏覽器中的構(gòu)造函數(shù)為 Worker()
但是必須傳入一個字符串參數(shù)作為 webWorker 運行的內(nèi)容,在上面的示例中便是傳入了 worker-5x10^9.js 來讓其執(zhí)行。
語法
new Worker(aURL);
參數(shù)
| 參數(shù)名 | 參數(shù)類型 | 參數(shù)描述 | 是否必要 |
|---|---|---|---|
| aURL | String | webWorker運行腳本的地址,它必須遵循同源策略 | TRUE |
用法
var worker = new Worker(workerURL);
實例API
我們可以看看輸出worker實例對象。

我們可以看到它主要包含4個方法:
- onerror
- onmessage
- postMessage
- terminate
十分簡單
onerror
這個方法是在Worker的error事件觸發(fā)時執(zhí)行,需要給onerror賦值一個函數(shù),并且會給該函數(shù)傳遞一個事件對象。
script of html
let worker = new Worker('worker-error.js');
worker.onerror=function(e){
console.log('錯誤對象',e);
}
worker-error.js
a=b;

這樣我們就可以獲取到內(nèi)部的錯誤信息,并加以處理,但是一般對這方面的需求很少,主要獲取內(nèi)部運行的情況。
onmessage
這個方法是在Worker的內(nèi)部執(zhí)行 postMessage 事件觸發(fā)時執(zhí)行,需要給onmessage賦值一個函數(shù),并且會給該函數(shù)傳遞一個事件對象。傳遞的值通過事件對象的 data 屬性獲取。
script of html
let worker = new Worker('worker-postMessage.js');
worker.onmessage=function(e){
console.log(e,e.data);
}
worker-postMessage.js
postMessage("I'm Worker");

這樣我們就可以獲取到內(nèi)部的發(fā)出信息。
postMessage(aMessage, transferList)
這個方法是向Worker的內(nèi)部發(fā)送消息,在這里不止是傳遞字符串,可以傳遞對象等,但是不能傳遞函數(shù)包含的對象。
他會對對象進行深度克隆,所以可以用來進行對象的異步的深度克隆。這一部分在下面進行詳細闡述。
| 參數(shù)名 | 參數(shù)類型 | 參數(shù)描述 | 是否必要 |
|---|---|---|---|
| aMessage | any | 需要發(fā)送的消息,可以傳遞對象,會對對象(可以包含js的內(nèi)置對象類型)進行深度克隆,但是不支持函數(shù)的傳遞 | FALSE |
| transferList | array | 將對象的上下文環(huán)境移交給worker | FALSE |
script of html
let worker = new Worker('worker-backData.js');
worker.onmessage=function(e){
console.log(e.data);
}
worker.postMessage('123');
worker.postMessage({x:1});
worker.postMessage({x:1,y:function(){}});
worker-postMessage.js
onmessage=function(e){
postMessage(JSON.stringify(e.data));
}

由于執(zhí)行是異步的,所以還是要主線程的JS執(zhí)行完畢后才能執(zhí)行對事件的響應(yīng),第一個錯誤是因為對有函數(shù)的對象解析導(dǎo)致的,所以我們只能看到前面兩個的字付串輸出。
terminate()
這個函數(shù)是立刻停止worker的運行,不會等待worker的運行完成。
script of html
let worker = new Worker('worker-backData.js');
worker.onmessage=function(e){
console.log(e.data);
}
worker.postMessage('123');
setTimeout(()=>{
worker.terminate();
worker.postMessage('223');
},1000)

我們可以看到這里只有 123 輸出,雖然依然可以向worker發(fā)送消息,但是沒什么卵用。
webWorker內(nèi)部環(huán)境
worker的執(zhí)行的環(huán)境和瀏覽器環(huán)境的有一些不同,瀏覽器內(nèi)全局變量是 window 而 worker內(nèi)部是DedicatedWorkerGlobalScope
在這個環(huán)境中無法使用window來獲取全局變量
script of html
let worker = new Worker('worker-window.js');
worker.onmessage=function(e){
console.log(e.data);
}
worker-window.js
window.postMessage('123');

我們可以看到window是沒有定義的,想要獲取我們需要使用 self
worker-self.js
self.postMessage('123');

這樣我們就可以獲取內(nèi)部的全局變量了,其實self在瀏覽器環(huán)境下也是指向全局環(huán)境的。
全局環(huán)境
worker的內(nèi)部環(huán)境當(dāng)然也和瀏覽器下的環(huán)境差不多,都含有相關(guān)的全局方法。
console
worker的內(nèi)部環(huán)境也是支持console.log來方便調(diào)試的,并且它的輸出會展示在瀏覽器的控制臺當(dāng)中
worker-self.js
console.log('console.log')
console.log('console.error')
console.info('console.info')

對AJAX和webSocket的支持
console.log(XMLHttpRequest)//function XMLHttpRequest() { [native code] }
console.log(WebSocket)//function WebSocket() { [native code] }
這兩個對象都是支持的,所以webWorker,可以負責(zé)數(shù)據(jù)請求的收發(fā)。
無法操作DOM
console.log(document)//error
worker內(nèi)部是無法操作DOM的,主要是為了線程安全,但是我們在采用VDOM的時候,便可以放入worker內(nèi)部處理VDOM。
postMessage
在worker的postMessage方法,已經(jīng)worker內(nèi)部的postMessage都是支持對象傳遞的,它可以傳遞滿足The structured clone algorithm | 結(jié)構(gòu)化克隆算法的對象。
script of html
let worker = new Worker('worker-transferList.js');
let a={x:1};
let obj={x:a,y:a,z:{x:1}}
worker.postMessage(obj);
worker-transferList.js
onmessage=function(e){
console.log(e.data.x===e.data.y);//true
console.log(e.data.x===e.data.z);//flase
};
同時在深復(fù)制的時候還保持了內(nèi)部的索引結(jié)構(gòu)。
transferList
最難弄清的其實是 postMessage 的 transferList 參數(shù)。
transferList數(shù)組類型
transferList數(shù)組類型必須為 ArrayBuffer, MessagePort and ImageBitmap
let worker = new Worker('worker-transferList.js');
let obj={x:1}
worker.onmessage=function(e){
console.log(e.data);
}
worker.postMessage(obj,[obj]);//error: Value at index 0 does not have a transferable type.
我們設(shè)置成 ArrayBuffer
let worker = new Worker('worker-transferList.js');
let bufArr=new ArrayBuffer(100);
let obj={x:bufArr}
console.log(bufArr.byteLength);//100
worker.onmessage=function(e){
console.log(e.data);
}
worker.postMessage(obj,[bufArr]);
console.log(bufArr.byteLength);//0
我們可以看到post成功了,但同時我們會發(fā)現(xiàn)bufArr.byteLength的長度改變了,因為byteLength的上下文已經(jīng)搬移到worker內(nèi)部了。
script of html
let worker = new Worker('worker-transferList.js');
let bufArr=new ArrayBuffer(100);
let obj={x:bufArr}
console.log(bufArr.byteLength);//100
worker.onmessage=function(e){
console.log(e.data);
}
worker.postMessage(obj,[bufArr]);
console.log(bufArr.byteLength);//0
worker-transferList.js
onmessage=function(e){
console.log('worker beforeSend',e.data.x.byteLength);
postMessage(e.data,[e.data.x]);
console.log('worker afterSend',e.data.x.byteLength);
};

Worker 和 SharedWorker
上述講述的都是頁面獨享 Worker 也叫 DedicatedWorker ,然而還有一個 SharedWorker。
DedicatedWorker 會在頁面關(guān)閉時隨之關(guān)閉,而 SharedWorker 會在所有關(guān)聯(lián)的頁面都關(guān)閉后才關(guān)閉。
shared.js
let worker = new SharedWorker('worker-shared.js');
worker.port.start();
worker.port.postMessage('123');
worker.port.onmessage=function(e){
console.log(e.data);
};
worker-shared.js
var x=0;
function add(){
setTimeout(()=>{
x++;
add();
},100);
}
add();
addEventListener("connect", function(e){
var port = e.ports[0];
port.addEventListener('message', function(e) {
port.postMessage(x);
});
port.start(); //用onmessage綁定,必須要顯式啟動端口通信
});
當(dāng)我們只在第一個頁面進行刷新時,輸出的都是0,但當(dāng)有兩個頁面時,刷新一個頁面輸出的內(nèi)容就不再只是0。
當(dāng)只有一個頁面時

當(dāng)有兩個頁面時

注意
- 我也不知道為何,直接寫在HTML中的sharedWorker并不會執(zhí)行。
總結(jié)
這里 webWorker 簡單的介紹就算完了,使用 webWorker 能減少頁面的擁塞,充分利用客戶的物理資源處理大量數(shù)據(jù)同時還可以用于封裝接口層,畢竟是支持AJAX和webSocket的~~~。
參考資料
END
2017-3-16 完成
2017-2-25 立項