JS事件循环

167 阅读3分钟

事件循环

浏览器的 js 是单线程的,也就是说同一时刻,最多也只有一个代码在执行,但是浏览器又可以很好的处理异步请求。这是为什么呢?

关于执行中的线程:

主线程:也就是 js 引擎执行的线程,这个线程只有一个,页面渲染,函数处理都在这个主线程上执行。
工作线程:也称幕后线程,这个线程可能存在于浏览器或 js 引擎内,与主线程是分开的,处理文件读取,网络请求等异步事件。

在执行代码过程中,如果遇到一些异步代码(比如setTimeout,ajax,promise.then以及用户点击等操作),那么浏览器就会将这些代码放到另一个线程(在这里我们叫做幕后线程)中去执行,在前端由浏览器底层执行,在 node 端由 libuv 执行,这个线程的执行不阻塞主线程的执行,主线程继续执行栈中剩余的代码。

当幕后线程(background thread)里的代码执行完成后(比如setTimeout时间到了,ajax请求得到响应),该线程就会将它的回调函数放到任务队列(又称作事件队列、消息队列)中等待执行。而当主线程执行完栈中的所有代码后,它就会检查任务队列是否有任务要执行,如果有任务要执行的话,那么就将该任务放到执行栈中执行。如果当前任务队列为空的话,它就会一直循环等待任务到来。因此,这叫做事件循环。

任务队列

那么,问题来了。如果任务队列中,有很多个任务的话,那么要先执行哪一个任务呢?其实(正如上图所示),js是有两个任务队列的,一个叫做 Macrotask Queue(Task Queue) 大任务, 一个叫做 Microtask Queue 小任务

Macrotask 常见的任务:

  1. setTimeout
  2. setInterval
  3. setImmediate
  4. I/O
  5. 用户交互操作,UI渲染

Microtask 常见的任务:

  1. Promise(重点)
  2. process.nextTick(nodejs)
  3. Object.observe(不推荐使用)

事件循环执行流程如下:

主线程执行同步代码,遇到异步的放入 enevt Table 中,执行指定操作,完成后进入 enevt queue 中,当同步的代码执行完毕,则执行event queue 中代码
console.log(1)
setTimeout(function() {
  //settimeout1
  console.log(2)
}, 0);
const intervalId = setInterval(function() {
  //setinterval1
  console.log(3)
}, 0)
setTimeout(function() {
  //settimeout2
  console.log(10)
  new Promise(function(resolve) {
    //promise1
    console.log(11)
    resolve()
  })
  .then(function() {
    console.log(12)
  })
  .then(function() {
    console.log(13)
    clearInterval(intervalId)
  })
}, 0);

//promise2
Promise.resolve()
  .then(function() {
    console.log(7)
  })
  .then(function() {
    console.log(8)
  })
console.log(9)

结果如下

1
9
7
8
2
3
10
11
12
13

async/await 又是如何处理的

async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    console.log('async2');
}
async1();
new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});
console.log('script end');

输出结果为


/** 
 * async1 start
 * async2
 * promise1
 * script end
 * async1 end
 * promise2
 * */

js 的异步事件,因为有事件循环机制,异步事件就是由事件驱动异步非阻塞的,上面的栗子已经很好证明了。所以 nodejs 适合处理大并发,因为有事件循环和任务队列机制,异步操作都由工作进程处理(libuv),js 主线程可以继续处理新的请求。缺点也很明显,因为是单线程,所以对计算密集型的就会比较吃力,不过可以通过集群的模式解决这个问题。