事件循环 (Event Loop) 是浏览器用于处理非同步事件的一种机制。它确保了 JavaScript 的执行能够同步进行,即使 JavaScript 是单线程运行的。下面是事件循环的工作流程:
- 执行栈(Call Stack): 当一段JavaScript代码开始执行时,它首先会进入到执行栈中。如果这段代码是一个函数,那么这个函数就会被放到栈的顶部。
- Web APIs: 当遇到非同步操作(如:
setTimeout
,XMLHttpRequest
等)时,该操作会被浏览器的Web APIs接管,执行栈会继续执行下一行代码,不会停下等待异步操作的结果。 - 任务队列(Task Queue): 一旦异步操作完成了(比如说
setTimeout
中指定的时间已过),回调函数就会被放入任务队列中。任务队列就是一个等待执行栈清空后执行的回调函数的列表。 - 事件循环: 事件循环的职责是监控执行栈和任务队列。如果执行栈为空,它就会检查任务队列。如果任务队列中有待执行的回调函数,事件循环就会将其从队列中取出,放到执行栈中去执行。
- 渲染队列(Render Queue): 当浏览器准备进行渲染时(通常是每16.7毫秒,对应于60fps),它会有自己的渲染队列来处理重绘和回流事件。如果执行栈和任务队列都是空的,事件循环会从渲染队列取出任务执行,以确保用户界面能够及时更新。
- 微任务队列(Microtask Queue): 除了常规的任务队列,还有一种叫作微任务(Microtask)的任务,比如Promise的回调。微任务队列的特点是在当前执行栈清空后,就会立即执行微任务队列中的所有任务,即使任务队列中有等待的任务。只有当微任务队列为空时,事件循环才会查看任务队列。
例子
假如我们有以下代码片段:
javascriptconsole.log('脚本开始'); setTimeout(function() { console.log('setTimeout'); }, 0); Promise.resolve().then(function() { console.log('promise1'); }).then(function() { console.log('promise2'); }); console.log('脚本结束');
执行顺序将会是这样的:
'脚本开始'
被打印到控制台,因为它是第一行同步代码。setTimeout
的回调被 Web APIs 接管,执行栈继续往下执行。Promise.resolve()
创建了一个Promise,它的回调被放入了微任务队列。'脚本结束'
被打印到控制台,因为它是同步代码。- 当前的同步代码已经执行结束,执行栈被清空。
- 事件循环首先检查微任务队列,发现有Promise的回调。
'promise1'
和'promise2'
依次被打印到控制台。- 微任务队列清空,事件循环现在检查任务队列。
setTimeout
的回调现在被事件循环从任务队列移到执行栈,打印'setTimeout'
。