WebWorker异步处理

HTML5的时代来到了,由于前端负责的数据处理越来越大,单单靠一个JS的单线程越来越力不从心,webWorker的出现能更好的解决该问题,这篇文章将讲述webWorker的使用。

简单的实例

使用webWorker我们可以将一些大量计算的事情放置在另一个线程进行处理,这时候我们页面不会被卡死,让网页使用流畅。

比如我们进行一个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秒的时间那么用户体验将是相当不好的,因为两秒种的时间用户都无法操作。

这时候一般只能使用 setTimeout 将计算拆除,或者请求服务器处理,但是都很麻烦,在这里我们使用 Worker 就可以了。

script of html


console.time('启动页面');

var worker=new Worker('worker-5x10^9.js');//50亿加法计算的函数文件

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可以避免大量的计算导致的拥塞,避免页面假死,充分利用客户端的物理资源。

下面我们来详细讲述如何使用 webWorker

Worker简介

兼容性

虽然作为HTM5的新特性,但是各大浏览器对 webWorker 的支持都是很好的,所以我们基本可以大胆的尝试在工程中使用。

image

构造函数

webWorker 在浏览器中的构造函数为 Worker()

但是必须传入一个字符串参数作为 webWorker 运行的内容,在上面的示例中便是传入了 worker-5x10^9.js 来让其执行。

语法

new Worker(aURL);

参数

参数名 参数类型 参数描述 是否必要
aURL String webWorker运行脚本的地址,它必须遵循同源策略 TRUE

用法


var worker = new Worker(workerURL);

实例API

我们可以看看输出worker实例对象。

image

我们可以看到它主要包含4个方法:

  1. onerror
  2. onmessage
  3. postMessage
  4. terminate

十分简单

onerror

这个方法是在Worker的error事件触发时执行,需要给onerror赋值一个函数,并且会给该函数传递一个事件对象。

script of html


let worker = new Worker('worker-error.js');

worker.onerror=function(e){
    console.log('错误对象',e);
}

worker-error.js


a=b;

image

这样我们就可以获取到内部的错误信息,并加以处理,但是一般对这方面的需求很少,主要获取内部运行的情况。

onmessage

这个方法是在Worker的内部执行 postMessage 事件触发时执行,需要给onmessage赋值一个函数,并且会给该函数传递一个事件对象。传递的值通过事件对象的 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

这样我们就可以获取到内部的发出信息。

postMessage(aMessage, transferList)

这个方法是向Worker的内部发送消息,在这里不止是传递字符串,可以传递对象等,但是不能传递函数包含的对象。

他会对对象进行深度克隆,所以可以用来进行对象的异步的深度克隆。这一部分在下面进行详细阐述。

参数名 参数类型 参数描述 是否必要
aMessage any 需要发送的消息,可以传递对象,会对对象(可以包含js的内置对象类型)进行深度克隆,但是不支持函数的传递 FALSE
transferList array 将对象的上下文环境移交给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

由于执行是异步的,所以还是要主线程的JS执行完毕后才能执行对事件的响应,第一个错误是因为对有函数的对象解析导致的,所以我们只能看到前面两个的字付串输出。

terminate()

这个函数是立刻停止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发送消息,但是没什么卵用。

webWorker内部环境

worker的执行的环境和浏览器环境的有一些不同,浏览器内全局变量是 window 而 worker内部是DedicatedWorkerGlobalScope

在这个环境中无法使用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

这样我们就可以获取内部的全局变量了,其实self在浏览器环境下也是指向全局环境的。

全局环境

worker的内部环境当然也和浏览器下的环境差不多,都含有相关的全局方法。

console

worker的内部环境也是支持console.log来方便调试的,并且它的输出会展示在浏览器的控制台当中

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,可以负责数据请求的收发。

无法操作DOM


console.log(document)//error

worker内部是无法操作DOM的,主要是为了线程安全,但是我们在采用VDOM的时候,便可以放入worker内部处理VDOM。

postMessage

在worker的postMessage方法,已经worker内部的postMessage都是支持对象传递的,它可以传递满足The structured clone algorithm | 结构化克隆算法的对象。

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
};

同时在深复制的时候还保持了内部的索引结构。

transferList

最难弄清的其实是 postMessagetransferList 参数。

transferList数组类型

transferList数组类型必须为 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.

我们设置成 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成功了,但同时我们会发现bufArr.byteLength的长度改变了,因为byteLength的上下文已经搬移到worker内部了。

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 会在页面关闭时随之关闭,而 SharedWorker 会在所有关联的页面都关闭后才关闭。

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绑定,必须要显式启动端口通信

});

当我们只在第一个页面进行刷新时,输出的都是0,但当有两个页面时,刷新一个页面输出的内容就不再只是0。

当只有一个页面时

image

当有两个页面时

image

注意

  1. 我也不知道为何,直接写在HTML中的sharedWorker并不会执行。

总结

这里 webWorker 简单的介绍就算完了,使用 webWorker 能减少页面的拥塞,充分利用客户的物理资源处理大量数据同时还可以用于封装接口层,毕竟是支持AJAX和webSocket的~~~。

参考资料

MDN_Worker

caniuse_Worker

END

2017-3-16 完成

2017-2-25 立项

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容