WebWorker简单复习
本文由 小茗同学 发表于 2018-04-28 浏览(3399)
最后修改 2018-05-02 标签:javascript webworker

本文demo:http://demo.haoji.me/2018/04/28-webworker/

JS是单线程语言

由于JS设计的初衷就是用来进行一些简单的用户交互以及DOM操作,为了避免复杂性,从一开始JS就被设计成一门单线程语言,现在如是,以后也不会变。

然后正是由于单线程的缘故,当我们需要在前端运行一些大运算量的代码时,浏览器肯定会陷入卡顿。为了解决前端大运算量问题,HTML5引入了WebWorker

WebWorker

WebWorker是浏览器为我们提供的一个可以在浏览器后台开启一个新的线程的API,使得运行在浏览器中的 js 有了多线程的能力。但是这并不意味这js本身就支持多线程,因为这种新线程有很多限制:

  • 不能操作DOM;
  • 受主线程控制;
  • 和普通JS相比有很多限制;

WebWorker有2种,一种是只能在当前页面使用的Worker,一种是可以再多个页面之间共享线程的SharedWorker,前者随着当前页面关闭而关闭,而后者在同域的前提下,可以被多个页面访问。

另外,虽然各类文章里面都把它叫叫WebWorker,其实它的真正名字就叫Worker

如何使用

WebWorker是在主线程中通过传入一个 js 文件的路径来实现的:

index.js:

// new完之后会立即执行
var worker = new Worker('./webworker.js');

webworker.js:

// webworker里面不能访问window,取而代之的是self,而且不能操作DOM
console.log('start');
for(var i=0; i<10000; i++) {
	console.log(i);
}
console.log('end');

self

前面说了,WebWorker里面不能访问window,取而代之的是self,它们无法访问DOM或者BOM对象,只能调用部分浏览器API,例如:

  • XMLHttpRequest
  • navigator
  • location(只读)
  • setTimeout、setInterval等;
  • Promise;
  • 等等;

当我们执行console.log(self)时(注意直接控制台执行是没用的,只能在代码里面写):

通信

WebWorker在主线程和子线程之间实现通信的方法有两个:发消息postMessage(data)和接收消息onmessage(e),双方都可以互相发送互相接收。

index.js:

let worker = new Worker ('./webworker.js');
worker.onmessage = (e) => {
	console.log(e.data);
}
worker.postMessage('你好,我是主线程!');

webworker.js:

onmessage = (e) => {
	console.log(e.data) // main thread got a message
}
// 也可以写成self.postMessage,self可以省略
postMessage('你好,我是子线程!');

终止WebWorker

// 在主线程中终止
worker.terminate()

// 在子线程中终止自身
self.close()

引入其他脚本

webworker.js中还可以引入其它JS文件:

// 引入一个脚本
importScripts('xxx.js');

// 引入多个脚本
importScripts('aaa.js', 'bbb.js', 'ccc.js');

postMseeage的值传递问题

postMseeage传递数据的过程其实是一个值拷贝的过程,会现将数据JSON.stringify之后再JSON.parse

postMseeage也可以传送二进制数据,但是当数据过大时,由于值拷贝,浏览器会再生成一个该文件的拷贝,这样可能会引起浏览器性能的问题,所以当传输较大数据时,可以直接将数据转移给另一个线程,而不进行值拷贝,只是这样会导致原线程无法再使用这些数据,也能够防止多个线程同时修改的情况发生,这叫做零拷贝,主要是依靠第二个参数:

// 指定传输的所有数据都是零拷贝
let data = new ArrayBuffer(64)
worker.postMessage(data, [data])

// 指定数据中的某个属性零拷贝
let obj = {a: 1, b: 2, c: 3}
worker.postMessage(obj, [obj.a, obj.c])

SharedWorker

关于共享线程我没怎么实测过,主线程中创建一个共享线程:

var sharedWorker = new SharedWorker('./sharedworker.js')
sharedWorker.port.start()
sharedWorker.port.postMessage('你好,我是主线程!');
sharedWorker.port.onmessage = (e) => {
	console.log(e.data);
};

子线程 sharedworker.js:

self.onconnect = (e) => {
	let port = e.ports[0];
	port.addEventListener('message', (e) => {
		console.log(e.data); // 特别注意,共享线程的console.log是看不到的
		port.postMessage('你好,我是SharedWorker!');
	});
	port.start();
}

总结

个人觉得,虽然浏览器提供了这么强大的东西,但是99%的场景下都是不需要用到WebWorker的,毕竟现在浏览器性能越来越好,而且在Web上也没有那么多大运算量的场景。关于WebWorker,简单了解一下就行。