JavaScript 是一门单线程语言,这意味着它一次只能执行一个任务。 但是,我们经常需要在 JavaScript 中执行一些耗时的操作,例如网络请求、定时器等等。 如果这些操作同步执行,会导致页面卡顿,用户体验非常差。 这时候,就需要用到 JavaScript 的事件循环机制来处理异步操作。
什么是事件循环?
你可以把事件循环想象成一个永动机,它不断地从任务队列中取出任务并执行。 整个过程大致如下:
- 调用栈 (Call Stack): JavaScript 引擎首先会执行调用栈中的代码,也就是我们编写的同步代码。 栈就像一个叠盘子,后进先出。 当遇到函数调用时,就将函数压入栈中;函数执行完毕,就从栈中弹出。
- 任务队列 (Task Queue): 当遇到异步操作时,例如
setTimeout
、Promise
,它们会被交给浏览器或 Node.js 的 API 处理。 这些 API 完成后,会将对应的回调函数放入任务队列中。 队列就像排队,先进先出。 - 事件循环 (Event Loop): 事件循环会不断地检查调用栈是否为空。 如果为空,它就会从任务队列中取出一个任务放入调用栈中执行。 如此循环往复,直到所有任务都执行完毕。
宏任务和微任务
任务队列实际上分为两种:宏任务队列和微任务队列。
- 宏任务 (Macro Task): 例如
setTimeout
、setInterval
、script
(首次执行的 script 代码块)、用户交互事件、I/O 操作等。 - 微任务 (Micro Task): 例如
Promise.then
、MutationObserver
、process.nextTick
(Node.js 环境) 等。
事件循环的执行顺序是:
- 执行一个宏任务 (通常是 script 代码块)。
- 检查是否存在微任务队列。 如果有,则执行所有微任务,直到微任务队列为空。
- 更新渲染。
- 重复以上步骤。
为什么要区分宏任务和微任务?
这样做是为了更好地控制任务的优先级。 微任务通常是一些需要在当前宏任务执行完毕后立即执行的任务,例如对 DOM 的修改。 将它们放在微任务队列中,可以确保在下一次渲染之前完成这些操作,避免页面出现闪烁等问题。
举个例子
让我们来看一个例子来理解事件循环:
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');
这段代码的执行顺序是:
script start
script end
promise1
promise2
setTimeout
解释:
- 首先,执行 script 代码块,输出
script start
和script end
。 - 然后,
setTimeout
被放入宏任务队列,Promise.resolve().then
被放入微任务队列。 - script 代码块执行完毕后,事件循环检查到微任务队列不为空,执行所有微任务,输出
promise1
和promise2
。 - 微任务队列为空后,事件循环取出一个宏任务 (也就是
setTimeout
的回调函数) 放入调用栈执行,输出setTimeout
。
总结
JavaScript 的事件循环机制是理解异步编程的关键。 掌握事件循环、宏任务和微任务的概念,可以帮助我们编写更高效、更稳定的 JavaScript 代码。 希望这篇文章能帮助你更好地理解 JavaScript 事件循环! 理解透彻事件循环,以后再面对异步编程,就不会感觉晕头转向啦,哈哈!