前言
JavaScript是一门单线程
的编程语言,这一特性决定了在任何给定时刻,JavaScript程序只能执行一个任务。然而,为了使JavaScript能够处理异步操作而不阻塞主线程,引入了Event Loop
(事件循环)的机制。本文将深入探讨JavaScript的Event Loop,解释它的工作原理,并通过案例帮助新手更好地理解这一关键概念。
在学习Event Loop之前,我们先给大家科普些小知识
什么是单线程?
单线程是指在任何给定时刻,程序或进程只能执行一个任务或代码块。在单线程模型中,代码是按照顺序逐行执行的,每个操作都要等待前一个操作的完成。
什么是宏任务?
宏任务代表的是那些较大粒度的任务,通常包括I/O 操作
、定时器事件(setTimeout
、setInterval
)、用户交互事件(点击
、输入
)等。每个宏任务都会在一个事件循环中执行,宏任务之间会按照一定的顺序排队执行。
js
// 宏任务
let a = 2
console.log(a);
setTimeout(() => {
console.log(1);
}, 0);
let b = 3
console.log(b);
在上面的例子中,整体代码执行是同步的,虽然setTimeout
设置了超时时间为0毫秒,但它仍然会被放入宏任务队列,而不是立即执行。这是因为setTimeout
的第二个参数表示最少等待的时间,而非确切的时间。所以这个回调函数会在当前宏任务执行完毕后,被放入宏任务队列等待执行。
什么是微任务?
微任务是相对于宏任务更小粒度的任务,典型的微任务包括Promise
的回调函数、process.nextTick
等。微任务的执行时机是在当前宏任务执行完毕、下一个宏任务开始之前。
js
console.log('start');
new Promise((resolve, reject) => {
console.log('123');
resolve();
})
// 微任务
.then(() => {
console.log('then');
})
console.log('end');
在上面的例子中,Promise
的构造函数是同步执行的。因此,这里会打印123到控制台。同时,通过resolve
方法将 Promise 的状态设置为resolved。然后在then
方法中注册了一个回调函数,这个回调函数会在微任务
队列中等待执行。由于Promise的状态是同步设置为resolved的,所以这个微任务会立即执行。
宏任务与微任务执行顺序与优先级
在一个宏任务中,当所有同步代码和宏任务执行完毕后,会检查微任务队列,依次执行其中的微任务。然后,选择下一个宏任务执行,这个过程不断循环。
js
console.log("Start");
setTimeout(() => {
console.log("这是宏任务");
}, 0);
Promise.resolve().then(() => {
console.log("这是微任务");
});
console.log("End");
在上述例子中,微任务的执行优先于下一个宏任务的开始,即使微任务是在当前宏任务中产生的。
什么是Event Loop?
Event Loop是JavaScript处理异步操作的核心机制。它使得JavaScript在执行同步任务的同时,能够异步执行一些任务,而不会阻塞主线程。这种机制通过一系列的步骤来实现,主要包括执行同步代码、执行异步代码、检查消息队列、执行消息队列中的任务等。
Event Loop的基本工作流程
1. 执行同步代码(这属于宏任务)
JS引擎首先会执行当前执行栈中的同步代码,这是按照代码的顺序逐行执行的部分。
2. 当执行栈为空,查询是否有异步代码需要执行
当执行栈为空时,JS 引擎会去查询是否有异步代码需要执行。这可以是注册的回调函数、定时器到期、事件触发等。
3. 执行微任务
如果有微任务(Promise 的回调、process.nextTick等),它们会在当前宏任务执行完毕后立即执行。微任务的执行时机优先于下一个宏任务开始。
4. 如果有需要,会渲染页面
在一些环境中,比如浏览器中,可能在这个时候进行页面的渲染。这确保了用户界面的及时更新。
5. 执行宏任务
如果有宏任务(定时器事件、I/O 操作、用户交互事件),它们会被放入宏任务队列中,等待下一轮 Event Loop 执行。
总结
整个过程是一个循环,不断地执行同步代码、查询异步任务、执行微任务、渲染页面以及执行宏任务。这个循环一直持续,形成了 JavaScript 异步执行的基本模型。