JavaScript是一种单线程的脚本语言,但它通过事件循环(Event Loop)机制来处理异步操作,使得非阻塞的I/O操作和定时器可以在单线程环境中工作。
事件循环的基本思想是在执行堆栈中的同步任务的同时,不断地从任务队列(Task Queue)中提取事件,执行相应的异步任务。整个过程可以概括为以下几个步骤:
执行同步任务:
- 从调用栈(Call Stack)中执行同步任务,直到调用栈为空。
执行微任务队列中的任务:
- 在同步任务执行完毕后,会检查微任务队列(Microtask Queue)中是否有任务。微任务包括
Promise
的then
回调、process.nextTick
等。 - 如果有微任务,将依次执行微任务队列中的所有任务。
- 在同步任务执行完毕后,会检查微任务队列(Microtask Queue)中是否有任务。微任务包括
执行事件队列中的任务:
- 从任务队列中取出一个任务,执行它。任务可能是定时器回调、事件回调等。
- 如果任务是一个宏任务(如
setTimeout
),执行完后,会检查微任务队列。
重复:
- 重复以上过程,不断从调用栈中执行同步任务,然后执行微任务,再执行宏任务,如此往复。
下面是一个简单的示例,演示了事件循环中的微任务和宏任务:
console.log('Start');
// 宏任务1:setTimeout
setTimeout(function() {
console.log('Timeout 1');
}, 0);
// 宏任务2:Promise
Promise.resolve().then(function() {
console.log('Promise 1');
});
console.log('End');
在这个例子中,首先输出Start
和End
,然后是微任务Promise 1
,最后是宏任务Timeout 1
。虽然setTimeout
的延时被设为0,但由于它是宏任务,会在当前宏任务执行完毕后才执行,而微任务会在当前宏任务执行结束、下一个宏任务开始前执行。这就是为什么Promise
的回调比setTimeout
的回调先执行的原因。