Web Worker

Web Worker

学习阮一峰老师的web worker而记录下来

一概述

JavaScript语言采用的是单线程,所有任务只能在一个线程上完成,一次只能做一件事。前面的任务没做完,后面的任务只能等着。随着电脑计算能力的增强,尤其是多核CPU的出现,单线程带来很大的不方便,无法充分发挥计算机的计算能力。Web worker的作用是为JavaScript创作多线程环境,允许主线程创建worker线程,将一些任务分配给后者运行。在主线程运行的同时,worker线程在后台运行,两者互不干扰。等到worker线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务,被worker线程负担了,主线程(通常负责UI交互)就会很流畅,不会被阻塞或拖慢。

Worker线程一旦新建成功,就会始终运行,不会被主线程上的活动(比如用户点击按钮,提交表单)打断。这样有利于随时响应主线程的通信。但是这也造成了worker比较耗费资源,不应该过度使用,而且一旦使用完毕,就应该关闭

Web worker有以下几个使用注意点

  1. 同源限制

分配给worker线程运行的脚本文件,必须与主线程的脚本文件同源。

  1. DOM限制

Worker线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的DOM对象,也无法使用document, window, parent这些对象。但是worker线程可以使用navigator, location(只读)对象

  1. 通信联系

Worker线程和主线程不再同一个上下文环境,他们不能直接通信,必须通过消息完成

  1. 脚本限制

Worker线程不能执行alert()方法confirm()方法,但可以使用XMLHttpRequest对象发出Ajax请求

  1. 文件限制

Worker线程无法读取本地文件,即不能打开本机的文件系统(file://),它所加载的脚本必须来自网络

二.基本用法

2.1主线程

主线程采用new命令,调用Worker()构造函数,新建一个Worker线程

Var worker = new Worker(‘worker.js’)

Worker()构造函数的参数是一个脚本文件,该文件就是Worker线程所要执行的任务。由于Worker不能读取本地文件,所以这个脚本必须来自网络。如果下载没有成功,Worker就会默默地失败。

然后主线程调用worker.postMassage()方法向Worker发消息

Worker.postMessage(‘hello world’)

Worker.postMessage({method: ‘echo’}, args: [‘Work’])

Worker.postMessage()方法地参数,就是主线程传给Worker地数据,可以是各种数据类型包括二进制

接着主线程通过worker.onmessage指定监听函数,接收子线程发回来地消息

Worker.onmessage = functioin (event) {

       Console.log(event.data)

       DoSomething()

}

Function doSomething(){

       //执行任务

       Worker.postMessage(‘work done!’)

}

上面代码中,事件对象地data熟悉可以获取worker发来地数据,worker完成任务以后主线程就可以把它关掉

Worker.terminate()

2.2Worker线程

Worker线程内部需要有一个监听函数,监听message事件

Self.addEventListener(‘message’, function (e) {

       Self.postMessage(‘you said:’ + e.data)

}, false)

上面代码中,self代表子线程自身,即子线程地全局对象,因此等同于下面两种写法

//写法一

This.addEventListerer(‘message’, function (e) {

       This.postMessage(‘you said:’ + e.data)

}, false)

AddEventListener(‘message’, function(e) {

       PostMessage(‘you said:’ + e.data)

},false)

除了使用addEventListener()指定监听函数,也可以使用self.onmessage指定。监听函数地参数是一个事件对象。它地data属性包含主线程发来地数据。Self.postMessage()方法用来向主线程发送消息

根据主线程发来地数据,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 stoped:’ + data.msg)

              Self.close()

              Break;

       Default:

              Self.postMessa(‘unknown:’ + data.msg)

}

},false)

Self.close()用于再worker内部关闭自身

2.3worker加载脚本

Worker内部如果要加载其它脚本,有一个专门的方法importScript()

ImportScript(‘script.js’)

加载多个脚本

ImportScript(‘script1.js’,’script2.js’,’script3.js’)

2.4错误处理

主线程可以监听worker是否发生错误。如果发生错误worker会触发主线程的error事件

Worker.onerror(function (event) {

       Console.log([

              ‘error:line’, event.loneno,’ in ’, event.filename, ‘ : ’,event.message

].join(‘’))

})

Worker.addEventListener(‘error’, function (event){

})

Worker内部也可以监听error事件

2.5关闭worker

使用完毕,为了节省系统资源,必须关闭worker

//主线程

Worker.terminate()

//worker线程

Self.close()

三数据通信

前面说过,主线程与worker之间的通信内容,可以是文本,也可以是对象。需要注意的是,这种通信是拷贝关系,即是传值而不是传地址。Worker对通信内容的修改,不会影响到主线程。事实上,浏览器内部的运行机制是先将通信内容串行化,然后把串行化后的字符串发给worker,后者再将它还原

主线程与worker之间也可以交换二进制数据,比如file, blob, arrayBuffer等类型。也可以在线程之间发送。

//主线程

Var uInt8Array = new Uint8Array(new ArrayBuffer(10))

For (var I = 0l I < uInt8Array.length; ++i) {

       uInt8Array[i] = I * 2 // [0,2,4,6,7,…….]

}

Worker.postMessage(uInt8Array)

 

//worker线程

Self.onmessage = function (e) {

       Var uInt8Array = e.data

       PostMessage(uInt8Array.toString())

       PostMessage(uInt8Array.byteLength)

}

但是,拷贝方式发送二进制数据会造成性能问题。比如主线程向worker发送一个500MB文件,默认情况下浏览器会生成一个原文件的拷贝。为了解决这个问题,JavaScript允许主线程把二进制数据直接转移给子线程,但是一旦转移,主线程就无法再使用这些二进制数据了,这是为了防止出现多个线程同时修改数据的麻烦。这种转移数据的方法叫做Transferable Objects。这使得主线程可以快熟把数据交给worker,对于影像处理,声音处理,3D运算等就非常方便了,不会产生性能负担

如果要直接转移数据的控制权,就要使用下面的写法

// Transferable Objects 格式

Worker.postMessage(arrayBuffer,[arrayBuffer])

 

//例子

Var ab = new ArrayBuffer(1)

Worker.posrMessage(ab,[ab])

四同页面的web worker

通常情况下,worker载入的是一个单独的JavaScript脚本文件,但是也可以载入与主线程在同一个网页的代码

<!DOCTYPE html>

<body>

       <script id=”worker” type=”app/worker”>

              AddEventListener(‘message’, function () {

                     PostMessage(‘some message’)

}, false)

       </script>

</body>

</html>

上面是一段嵌入网页的脚本,注意必须指定<script标签的type属性是一个浏览器不认识的值,上例子是app/worker

然后读取这一段嵌入页面的脚本用worker来处理

Var blob = new Blob([document.querySeletor(‘#worker’).textContent])

Var url = window.URL.createObjectURL(blob)

Var worker = new Worker(url)

 

Worker.onmessage = function (e) {

       //e.data === ‘some message’

}

上面代码中,先将嵌入网页的脚本代码,专程一个二进制对象,然后为这个二进制对象生成URL.再让worker加载这个URL.这样就做到了,主线程与worker的带都再同一个网页上面

五实例:worker线程完成轮询

有时候浏览器需要轮询服务器状态,以便第一时间得知状态改变。这个工资可以放在worker里面

Function createWorker(f){

       Var blob = new Blob([‘(’ + f.toString() + ‘)()’])

       Var url = window.URL.createObjectURL(blob)

       Var worker = new Worker(url)

       Return worker

}

 

Var pollingWorker = createWorker (function(e){

       Var cache

       Function compare (new, old) {…}

       SetIntval(function () {

              Fetch(‘/my-api-endpoint’).then(function (res){

                     Var data = res.json()

                    

                     If (!compare(data,cache)){

                            Cache = data

                            Self.postMessage(data)

}

})

},1000)

})

 

PollingWorker.onmessage = function () {

       // render data

}

PollingWorker.postMessage(‘int’)

上面代码中,worker每秒钟轮询一次数据,然后跟缓存做比较。如果不一致说明服务端有了新的变化,因此就要通知主线程

六实例:worker新建worker

Worker线程内部还能再新城worker线程(目前只有firefox浏览器支持)。下面的例子是将一个计算密集的任务分配到10个worker

主线程:

Var worker = new Worker(‘worker.js’)

Worker.onmssage = function(event){

       Document.getElementById(‘result’).textContent = event.data

}

Worker线程代码如下:

// worker.js

 

//settings

Var num_worker = 10

Var items_per_worker = 1000000

 

//start the workers

Var result = 0

Var pending_workers = num_workers

For (var I = 0; I < num_workers; i+=1) {

       Var worker = new Worker(‘core.js’)

       Worker.postMessage(I * items_worker)

       Worker.postMessage((i+1) * items_per_worker)

       Worker.onmessage = storeResult

}

 

//handle the results

Function storeResult (event) {

       Result += event.data

       Pending_worker -=1

       If (pending_worker <= 0) {

 

       PostMessage(result) // finished!

}

}

上面代码中,worker线程内部新建10个worker线程,并且依次向这10个worker发送消息,告知了计算的七点和终点。计算任务脚本的代码如下

//core.js

Var start

Onmessage = getStart

Function getStart(event){

       Start = event.data

       Onmessage = getEnd

}

 

Var end

Function getEnd(event){

       End = event.data

       Onmessage = null

       Work()

}

 

Function work() {

       Var result = 0

       For (var I = start; I < end; I += 1) {

              //perform some complex calculation here

              Result += 1

}

PostMessage(result)

Close()

}

七 API

7.1主线程

浏览器原生提供winker()构造函数,用来供主线程生成worker线程

Var myWorker = new Worker(jsURL,options)

Worker()构造函数,可以接受两个参数。第一个参数是脚本的网址(必须遵守同源策略),该参数是必须的,且只能加载JS脚本。第二个参数是配置对象,该对象可选。它的一个作用就是指定worker的名称,用来区分多个worker线程

// 主线程

Var myWorker = new Worker(‘worker.js’, {name: ‘myWorker’})

 

//worker线程

Worker()构造函数返回一个worker线程对象,用来供主线程操作worker。Worker线程对象的属性和方法如下

Worker.onerror:指定error事件的监听函数

Worker.onmessa:指定messa事件的监听函数,发怂过来的数据再Event.data属性中

Worker.onmessageerror:指定messageerror事件的监听函数。发送的数据无法序列号称字符串时,会触发这个事件

Worker.postMessage():向worker线程发送消息

Worker.termitnate():立即终止worker线程

7.2worker线程

Web worker有自己的全局对象,不是主线程的window,而是一个专门为Worker定制的全局对象,因此定义再window上面的对象和方法不是全部都可以

Worker线程有一些自己的全局属性和方法

Self.nama: worker的名字,该属性只读,由构造函数指定

Self.onmessageerror:指定messageerror事件的监听函数。发送的树无法序列号成字符串时,会触发这个事件

Self.close():关闭worker线程

Self.postMessage():向产生这个worker线程发送消息

Self.importScript():加载JS脚本

 

转载于:https://www.cnblogs.com/maggie-pan/p/10061658.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值