Featured image of post JS: 事件循环机制

JS: 事件循环机制

在传统的同步编程中,代码按照顺序执行,每个操作都会阻塞程序的执行,直到完成。然而,在Web开发中,由于网络请求、用户交互等操作可能会花费很长时间,同步编程模型将导致用户体验不佳。比如 Ajax 请求,如果使用同步编程模型,那么在请求完成之前,页面会一直处于加载状态,用户无法进行其他操作,对于用户的体验来说是非常糟糕的。所以,我们需要一种异步编程模型,来解决这个问题。

我们都知道 JS 是一门单线程语言,它也包含了 同步异步 的概念,同步是指代码按照顺序执行,能够直接得到预期的结果,而异步是指代码执行后,不会立即得到结果,而是在将来的某个时间点才会得到结果。而单线程带来的好处是不需要考虑切换线程的开销,也不需要考虑线程安全的问题,但是也带来了一个问题,就是如果某个任务需要花费很长时间,那么后面的任务就必须等待,这样就会导致程序的执行效率很低。

所以,为了解决这个问题,JS 引入了 事件循环机制 ,它是一种异步编程模型,用来处理异步任务。事件循环机制的核心是 事件循环 ,它是一个程序结构,用来等待和发送消息和事件。在事件循环中,存在一个 消息队列 ,用来存放消息和事件。当异步任务完成时,就会将对应的消息或事件放入消息队列中,然后等待执行。当主线程中的任务执行完毕后,就会从消息队列中取出消息或事件,然后执行对应的回调函数。

事件循环可以用类似如下的伪代码来表示:

1
2
3
while (queue.waitForMessage()) {
  queue.processNextMessage()
}

queue.waitForMessage() 表示等待消息,queue.processNextMessage() 表示处理消息。当消息队列中有消息时,就会一直循环下去。

任务队列

事件循环中有两种任务队列,分别是宏任务队列和微任务队列。宏任务队列用来存放宏任务,微任务队列用来存放微任务。宏任务和微任务的执行时机是不同的,当宏任务队列中的任务执行完毕后,会立即执行微任务队列中的任务。

宏任务

宏任务代表一组相对较大的、独立的任务,例如整体的代码块、setTimeout、setInterval等。

宏任务包括以下几种:

  • <script> 标签
  • setTimeoutsetInterval
  • setImmediate ( Node.js 环境 )
  • requestAnimationFrame ( 浏览器环境 )
  • I/O
  • UI 渲染

微任务

微任务代表一组相对较小的、独立的任务,例如 Promise 中的回调函数(Promise 本身不属于微任务)、MutationObserver 中的回调函数等。

微任务包括以下几种:

  • process.nextTick ( Node.js 环境 )
  • Promise.thenPromise.catchPromise.finally
  • MutationObserver

事件循环机制

事件循环机制的执行流程如下:

  1. 执行全局 script 中的代码,这属于宏任务
  2. 执行微任务队列中的任务,直到微任务队列为空
  3. 执行宏任务队列中的任务,直到宏任务队列为空
  4. 执行浏览器 UI 线程的渲染工作
  5. 执行下一轮事件循环,回到第 2 步

我们可以通过下面的例子来理解事件循环机制:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
console.log('script start')

setTimeout(function() {
  console.log('setTimeout')
}, 0)

Promise.resolve().then(function() {
  console.log('promise1')
}).then(function() {
  console.log('promise2')
})

console.log('script end')

上面的代码中,setTimeoutPromise 都是异步任务,所以会先执行同步任务,然后执行微任务,最后执行宏任务。

执行结果如下:

1
2
3
4
5
script start
script end
promise1
promise2
setTimeout

一开始先执行同步任务,输出 script start ,然后遇到 setTimeout 时将其放入宏任务队列中,遇到 Promise 时将其放入微任务队列中,然后执行同步任务,输出 script end ,此时同步任务执行完毕,开始执行微任务队列中的任务,输出 promise1 ,然后执行微任务队列中的任务,输出 promise2 ,此时微任务队列中的任务执行完毕,开始执行宏任务队列中的任务,输出 setTimeout

参考资料

Licensed under CC BY-NC-SA 4.0