前言
去年在某家公司面试时,最后环节CTO问关于 web worker 的问题,直接坦白工作中没用过,不怎么了解,然后被点评为技术能力不错,但是没有进行过系统性学习,有待提高。好吧,那就从哪跌倒从哪爬起来,今年看了很多基础的东西,但是看完之后很快就忘了,所以在此记录下来,以备复习。
一、what
到底啥是 web worker, 可以简单的理解成,HTML5规范中提供了一种API,通过这个 API 可以在主线程之外生成其他线程,并且其他线程在后台执行,不会影响主线程的运行。
二、how
2.1 基础用法
先来一个最简单的 hello world 。
注意:建议使用 http-server 启动一个静态服务器,因为 Worker() 无法加载本地文件(file:///xxxxx)。
index.js
// 创建 worker 线程
const worker = new Worker('worker-1.js');
// 向 worker 线程发送消息
worker.postMessage('hello world');
// 接收 worker 线程发送过来的消息
worker.onmessage = function (e) {
console.log(e.data);
// 关闭 worker 线程
worker.terminate();
}
worker-1.js
// 接收主线程发送过来的消息
self.addEventListener('message', function (e) {
// 向主线程发送消息
self.postMessage('I received ' + e.data);
})
运行结果:
demo.png
2.2 主线程
这里要特别说明,这里讲的主线程是指浏览器`的 js 引擎线程。
JavaScript 的宿主环境浏览器是多进程的,chrome 浏览器的一个tab相当于一个渲染进程,而一个进程中包括多个线程。
示意图.png
详细请阅读文章 从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理
2.3 worker 线程
主线程通过 Worker() 创建了 worker 线程,注意:js 仍然是单线程的,这个 worker 线程实际上是浏览器创建的,只是可以被 js主线程操作。
const myWorker = new Worker(aURL, options);
aURL代表一个脚本文件的路径,该脚本就是 worker 线程要执行的任务。
2.4 worker 线程的全局对象(上下文)
在 worker 线程中,它的全局对象就是自身,即 this 指向的是 worker 线程自身。
这里需要注意的是,self 也指向的是 worker 线程自身
总结:使用worker线程属性或方法的 3 种方法
- this.Fn()
- self.Fn()
- Fn()
2.5 数据通信
主线程向子线程发送消息
worker.postMessage()
子线程接收主线程发送的消息
// 第一种方法
addEventListener('message', function (e) {
}, false);
// 第二种方法
onmessage = function(e) {}
子线程向主线程发送消息
postMessage()
主线程接收主线程发送的消息
// 第一种方法
worker.addEventListener('message', function (e) {
}, false);
// 第二种方法
worker.onmessage = function(e) {}
2.6 数据通信的内容
主线程和 worker 线程之前的通信是拷贝关系,即是传值,而不是传址。worker 线程对通信内容的修改,不会影响到主线程,即需要将修改后的数据发送给主线程才行。
浏览器内部运行机制:先将通信内容串行化,然后把串行后的字符串传送给 worker,worker 线程再将它还原。
对二进制数据的传送
二进制数据过大,拷贝时容易导致性能问题。
javaScript 允许主线程把二进制数据转移给子线程,但是为了避免多个线程同时修改数据,所以转移后,主线程失去操作数据的能力。详细阅读 Transferable Objects
// Transferable Objects 格式
worker.postMessage(arrayBuffer, [arrayBuffer]);
2.7 错误处理
监听 error 事件
worker.onerror(function (event) {
console.log([
'ERROR: Line ', event.lineno, ' in ', event.filename, ': ', event.message
].join(''));
});
// 或者
worker.addEventListener('error', function (event) {
// ...
});
worker线程内同上述方法。
2.8 关闭worker
主线程关闭 worker 线程
worker.terminate();
子线程关闭自身
self.close()
2.9 worker 加载脚本
worker 线程中可以通过 importScripts() 加载其他脚本。
importScripts('script1.js', 'script2.js');
2.10 worker 线程的内容和主线程在同一个页面
不感兴趣可忽略此小节,不建议此种方式使用 worker ,违反单一职责原则。
步骤:
- 指定<script>标签的type属性是一个浏览器不认识的值,这样浏览器就不会解析运行这段脚本。
- 通过 id 获取元素内容
- 将脚本内容转换成 blob 格式
- 通过
window.URL.createObjectURL(blob)生成 url
三、why
3.1 注意事项
- 同源限制
分配给 worker 线程运行的脚本,必须与主线程的脚本文件同源。 - DOM 限制
worker 线程无法使用window、document、parent对象,可以使用location、navigator对象。 - 脚本限制
worker 线程不能执行alert()和confirm()方法,但可以使用 XMLHttpRequest 对象发出 AJAX 请求。 - 文件限制
worker 线程无法读取本地文件,即不能打开本机的文件系统 (file://) , 它所加载的文件必须来源于网络。
3.2 worker 的种类
- Dedicated Workers
专用:只能在一个script中使用 - Shared Workers
共享:可被不同的窗体的多个脚本运行
3.3 应用场景
- 复杂的数学计算
- 流媒体、图像处理
为什么web worker可以在前端开多线程,解决单线程卡死页面的问题,但是没有得到广泛使用?







网友评论