基本概念
JS 单线程:我们都知道 JavaScript 它是一个单线程的语言,同一时间只能做一件事。比如:在浏览器中,某一时刻我们在操作 DOM,你们这个时刻我们就不能去运行 JavaScript 代码,反过来也是,当我们在运行 JavaScript 代码的时候,我们也不能去操作 DOM,这个也就是 JS 的单线程。为什么要 JS 成单线程?因为在浏览器环境下,如果是多线程的,也就是操作 DOM 和运行 JavaScript 代码是并行处理的话,假如某个时刻,浏览器正在绘制 DOM,但是这个时候的 JavaScript 代码改变了 DOM,这样就会造成一个不一致,因此 JS 就被设计成了一门单线程语言。
虽然 JS 是单线程,但是它的宿主,我们的浏览器环境(node 环境)它是一个多线程的,在浏览器和 node 里面只有一条 JS 线程,但是还有很多其他的线程。以浏览器为例,浏览器的常驻线程(如下)大概有前三个:第一个,UI 线程,也就是 DOM 浏览器元素的回流和重绘,这个 UI 线程与 JavaScript 线程是互斥的。第二个就是 JavaScript 线程,单线程的运行 JS 代码。第三个是 GUI 线程,它主要是处理与用户交互的一些逻辑,比如点击某一个元素,拖动了或者缩放了这个就是由 GUI 线程处理的。除此之外还有 NetWorker 线程,网络线程,发送 ajax 请求,发送 http 请求都是走的 network 线程。还有 File 线程,读取文件。还有一个定时器线程。
UI 线程 - 回流和重绘 - 与 Javascript 线程互斥
JavaScript 线程 - 单线程运行 JavaScript
GUI 线程 - 交互线程
Network 线程
File 线程
定时器线程
这里可能会有一个问题是 ajax 请求对于 JS 来说是异步的,但是 JavaScript 是单线程的,这样就涉及到了一个基于事件的驱动。当我们发起一个网络请求时,浏览器会把这一部分网络请求交给 network 线程去处理,然后 JavaScript 线程等待 network 的指令驱动。在没有代码要运行的情况下,JavaScript 线程始终是空闲的,有了事件驱动之后,它会一直处于一个轮询的状态(Event Loop),浏览器会不断查询目前是否有 JavaScript 线程需要运行,如果有就运行,没有就保持闲置。当一个网络请求发送出去,这个时候的 JavaScript 线程是处于闲置的,但是浏览器还是会不停的询问,当 network 线程结束后,浏览器发现有新的 JavaScript 代码需要执行,它就会驱动 JavaScript 的线程去处理网络请求返回的结果,这个就是 JS 基于事件驱动的模型。
Event Loop(事件轮询图)
异步是将耗时比较长的任务放置到 Event Queue 事件队列的尾部。
WebWorker
WebWorker 为了解决浏览器假死这个问题而孕育而生的一项新技术。它是多线程模型,也是基于宿主。它属于 JavaScript 线程中的一个子线程,它完全受主线程控制,但是在 WebWorker 里面是不能操作 DOM 的。因为上面提到的 UI 线程和 JavaScript 线程是互斥的,这个互斥也就保证了 DOM 的唯一性,因此主的基调不能改变,但是需要有一个新的线程来分担繁杂的计算任务,这个也就是 WebWorker。
浏览器的兼容性
应用场景
WebWorker 是为了处理影响 UI 线程的 JavaScript 运算。因为在同一时刻,UI 线程和 JavaScript 线程只能有一个在运行,如果这个时候 JS 的线程承担过多运算的话,它的耗时就变得很长,这个时候的 UI 线程是没有反应的,这样就造成了页面的假死。
WebWorker 特点
- 一旦新建就会始终运行,不会被主线程打断。即使主线程卡死了,WebWorker 依然在运行。
- 同源限制,对于同一个 WebWorker 来讲,只有同源的网页才能够访问。
- 不能操作和访问 DOM(window、document),因为要保证 DOM 的唯一性。
- 不能使用包含交互的全局方法(alert、confirm),但是可以使用 XMLHttpRequest、setTimeout、setInterval
- 不能读取本地文件(不止 WebWorker 不能读取,JavaScript 主线程也不能读取),出于安全性的考虑,浏览器是不允许 js 读取本地文件的。
- WebWorker 分为两个:dedicated web worker(专用线程),只有一个网址一个页面可以使用这个线程和 shared web worker(共享线程),多个同源的网页可以共享一个 WebWorker,这样为跨页面通信提供了一种可能。
基本用法
-
创建 WebWorker
const webWorker = new Worker('main.js'); let result = 0; const fibonacci = (n) => {if (n <= 1) return 1; return fibonacci(n - 1) + fibonacci(n - 2); } result = fibonacci(10); console.log('result', result);
-
向 WebWorker 发送消息 (数据)
webWorker.postMessage({number : 10});
-
WebWorker 接收消息
webWorker.addEventListener('message', event => {console.log('received webworker data', event.data); }, false);
-
WebWorker 发送消息 (返回数据)
this.postMessage(returnValue);
-
主线程接收 WebWorker 消息
webWorker.addEventListener('message', event => {console.log('received webworker data', event.data); }, false);
-
关闭 WebWorker
方式一:在主线程关闭 WebWorker webWorker.terminate(); 方式二:在子线程,WebWorker 内部自己调用自己的 close 方法,不再接收新的 Macrotask(宏任务) this.close();
WebWorker 调用脚本
importScripts('./one.js', './two.js');
WebWorker 错误监听
webWorker.addEventListener('error', error => {console.error(error.filename, error.lineno, error.message);
});