事件循环

0. 为什么有事件循环

我们知道 JS 是单线程非阻塞执行的。单线程很好理解,可以避免多线程同时对某些资源操作时的混乱,比如多同一个 DOM 的操作等。那非阻塞的实现,就不可避免的要说到事件循环,事件循环在单线程上实现了异步的执行。

1. 执行栈和事件队列

首先说明两个概念。
第一个是执行栈。JS 的函数在执行时,会先在执行栈中加入执行的环境,也就是Context,也叫执行上下文。举个简单的例子:

(function foo(i) {
if (i === 3) {
return;
}
else {
foo(++i);
}
}(0));

上述代码中,每次调用 foo 函数,都会在执行栈中推入一个 context,其中每次的 i 都不同。然后在每一个环境被执行之后,就从栈中移除。
第二个是事件队列。当异步操作结束后,回调将被挂起,然后加入到事件队列,并继续执行当前执行栈中的环境。一直到当前执行栈中所有的任务被执行完,主线程处于空闲状态,则从事件队列中取出第一个事件,开始执行。

2. micro task 和 macro task

按照上面的说法,我们来看一段代码

setTimeout(()=>{console.log(1)}); 
new Promise(function(resolve, reject){
  console.log(2);
  resolve();
}).then(function(){
  console.log(3);
});

按照上面的说法,执行顺序应该是这样的:
开始事件队列为空,主线程闲置,将 setTimeout 押入执行栈,输出1;
然后执行 promise 内部代码,输出2;
紧接着执行 then 中传入的 resolve,输出3。
但是实际的输出是 2, 3 ,1。
这就涉及到另一个概念,就是 micro task 和 macro task。
事实上,当当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。
这就可以解释了以上代码。

ps:

以上是在浏览器中的事件循环执行过程,在 node 中的执行会有所不同。

你可能感兴趣的:(事件循环)