在传统的同步编程中,代码按照顺序执行,每个操作都会阻塞程序的执行,直到完成。然而,在Web开发中,由于网络请求、用户交互等操作可能会花费很长时间,同步编程模型将导致用户体验不佳。比如 Ajax 请求,如果使用同步编程模型,那么在请求完成之前,页面会一直处于加载状态,用户无法进行其他操作,对于用户的体验来说是非常糟糕的。所以,我们需要一种异步编程模型,来解决这个问题。
我们都知道 JS 是一门单线程语言,它也包含了 同步 和 异步 的概念,同步是指代码按照顺序执行,能够直接得到预期的结果,而异步是指代码执行后,不会立即得到结果,而是在将来的某个时间点才会得到结果。而单线程带来的好处是不需要考虑切换线程的开销,也不需要考虑线程安全的问题,但是也带来了一个问题,就是如果某个任务需要花费很长时间,那么后面的任务就必须等待,这样就会导致程序的执行效率很低。
所以,为了解决这个问题,JS 引入了 事件循环机制 ,它是一种异步编程模型,用来处理异步任务。事件循环机制的核心是 事件循环 ,它是一个程序结构,用来等待和发送消息和事件。在事件循环中,存在一个 消息队列 ,用来存放消息和事件。当异步任务完成时,就会将对应的消息或事件放入消息队列中,然后等待执行。当主线程中的任务执行完毕后,就会从消息队列中取出消息或事件,然后执行对应的回调函数。
事件循环可以用类似如下的伪代码来表示:
|
|
queue.waitForMessage() 表示等待消息,queue.processNextMessage() 表示处理消息。当消息队列中有消息时,就会一直循环下去。
任务队列
事件循环中有两种任务队列,分别是宏任务队列和微任务队列。宏任务队列用来存放宏任务,微任务队列用来存放微任务。宏任务和微任务的执行时机是不同的,当宏任务队列中的任务执行完毕后,会立即执行微任务队列中的任务。
宏任务
宏任务代表一组相对较大的、独立的任务,例如整体的代码块、setTimeout、setInterval等。
宏任务包括以下几种:
<script>
标签setTimeout
和setInterval
setImmediate
( Node.js 环境 )requestAnimationFrame
( 浏览器环境 )- I/O
- UI 渲染
微任务
微任务代表一组相对较小的、独立的任务,例如 Promise
中的回调函数(Promise
本身不属于微任务)、MutationObserver
中的回调函数等。
微任务包括以下几种:
process.nextTick
( Node.js 环境 )Promise.then
、Promise.catch
、Promise.finally
MutationObserver
事件循环机制
事件循环机制的执行流程如下:
- 执行全局 script 中的代码,这属于宏任务
- 执行微任务队列中的任务,直到微任务队列为空
- 执行宏任务队列中的任务,直到宏任务队列为空
- 执行浏览器 UI 线程的渲染工作
- 执行下一轮事件循环,回到第 2 步
我们可以通过下面的例子来理解事件循环机制:
|
|
上面的代码中,setTimeout
和 Promise
都是异步任务,所以会先执行同步任务,然后执行微任务,最后执行宏任务。
执行结果如下:
|
|
一开始先执行同步任务,输出 script start
,然后遇到 setTimeout
时将其放入宏任务队列中,遇到 Promise
时将其放入微任务队列中,然后执行同步任务,输出 script end
,此时同步任务执行完毕,开始执行微任务队列中的任务,输出 promise1
,然后执行微任务队列中的任务,输出 promise2
,此时微任务队列中的任务执行完毕,开始执行宏任务队列中的任务,输出 setTimeout
。