WebWorker異步處理

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

image

在我的電腦上是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);

image
image

使用webWorker可以避免大量的計算導(dǎo)致的擁塞,避免頁面假死,充分利用客戶端的物理資源。

下面我們來詳細講述如何使用 webWorker

Worker簡介

兼容性

雖然作為HTM5的新特性,但是各大瀏覽器對 webWorker 的支持都是很好的,所以我們基本可以大膽的嘗試在工程中使用。

image

構(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實例對象。

image

我們可以看到它主要包含4個方法:

  1. onerror
  2. onmessage
  3. postMessage
  4. 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;

image

這樣我們就可以獲取到內(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");

image

這樣我們就可以獲取到內(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));
}

image

由于執(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)

image

我們可以看到這里只有 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');

image

我們可以看到window是沒有定義的,想要獲取我們需要使用 self

worker-self.js


self.postMessage('123');

image

這樣我們就可以獲取內(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')

image

對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

最難弄清的其實是 postMessagetransferList 參數(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);
};

image

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)只有一個頁面時

image

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

image

注意

  1. 我也不知道為何,直接寫在HTML中的sharedWorker并不會執(zhí)行。

總結(jié)

這里 webWorker 簡單的介紹就算完了,使用 webWorker 能減少頁面的擁塞,充分利用客戶的物理資源處理大量數(shù)據(jù)同時還可以用于封裝接口層,畢竟是支持AJAX和webSocket的~~~。

參考資料

MDN_Worker

caniuse_Worker

END

2017-3-16 完成

2017-2-25 立項

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

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

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