并发模型和事件循环
JavaScript 有一个基于事件循环的并发模型,事件循环负责执行代码、收集和处理事件以及执行队列中的子任务。
JavaScript运行时
在执行 JavaScript 代码的时候,JavaScript 运行时实际上维护了一组用于执行 JavaScript 代码的代理。每个代理由一组执行上下文的集合、执行上下文栈、主线程、一组可能创建用于执行 worker 的额外的线程集合、一个任务队列以及一个微任务队列构成。除了主线程(某些浏览器在多个代理之间共享的主线程)之外,其他组成部分对该代理都是唯一的。
我们的着重点在于,当JavaScript运行时有三个东西
- 主线程
- 一个任务队列
- 一个微任务队列
理解这三个东西就能理解JavaScript的运行规则了。
同步和异步
同步的代码就是按照书写顺序指向的,异步却不同,有时后面的代码会先执行。
(function () {
console.log("这是开始");
setTimeout(function cb() {
console.log("这是来自第一个回调的消息");
});
console.log("这是一条消息");
setTimeout(function cb1() {
console.log("这是来自第二个回调的消息");
}, 0);
console.log("这是结束");
})();
// "这是开始"
// "这是一条消息"
// "这是结束"
// "这是来自第一个回调的消息"
// "这是来自第二个回调的消息"
这段代码先执行了 console.log("这是开始");然后调用 setTimeout(function cb() {
console.log("这是来自第一个回调的消息"); });这段代码中setTimeout是一个异步方法,它设置了一个定时器,等定时器的时间到了之后执行里面的回调函数。然后执行console.log("这是一条消息");再设置一个定时器 setTimeout(function cb1() {console.log("这是来自第二个回调的消息");}, 0);并把时间设为0,最后执行console.log("这是结束");
通过上面这段代码,我们发现了两种机制。
- 同步代码:同步代码会立刻执行
- 异步代码:异步代码会被先放到一个地方去,等同步代码执行完毕之后开始执行
浏览器的Event Loop
Event Loop就是事件循环,目前JavaScript主要运行在浏览器和node.js中,我们先讨论在浏览器中它是如何运作的。
首先我们了解一下为什么叫做事件循环,这是因为它常常是按如下方式被实现
while (queue.waitForMessage()) {
queue.processNextMessage();
}
queue.waitForMessage() 会同步地等待消息到达 (如果当前没有任何消息等待被处理)。
现在我们理解了事件循环是什么,可以开始看它的流程了
- 主线程每次执行时,先看看要执行的是同步代码还是异步的代码
- 如果是同步的代码就执行
- 遇到异步代码就把他交给对应的异步线程,自己还是继续执行同步代码
- 异步线程将异步代码放到消息队列上,等待执行
- 同步代码执行完成后,就看一下消息队列有没有任务
- 发现消息队列有任务就按进入的顺序执行,直到为空
- 从1开始重新执行流程
这里我们要注意两点
- 当执行来自任务队列中的任务时,在每一次新的事件循环开始迭代的时候运行时都会执行队列中的每个任务。在每次迭代开始之后加入到队列中的任务需要在下一次迭代开始之后才会被执行。
- 每次当一个任务退出且执行上下文栈为空的时候,微任务队列中的每一个微任务会依次被执行。不同的是它会等到微任务队列为空才会停止执行——即使中途有微任务加入。换句话说,微任务可以添加新的微任务到队列中,这些新的微任务将在下一个任务开始运行之前,在当前事件循环迭代结束之前执行。