面试题答案
一键面试JavaScript事件循环工作原理
- 调用栈(Call Stack):
- 调用栈是一种数据结构,用于记录函数调用的顺序。当JavaScript引擎执行代码时,每调用一个函数,就将该函数添加到调用栈的栈顶;函数执行完毕后,从栈顶移除。例如:
function add(a, b) {
return a + b;
}
function multiply(c, d) {
let result = add(c, d);
return result * 2;
}
multiply(3, 4);
在上述代码中,首先调用multiply
函数,multiply
函数被压入调用栈。然后在multiply
函数内部调用add
函数,add
函数又被压入调用栈。add
函数执行完毕返回结果后,从调用栈移除。接着multiply
函数继续执行并返回结果,最后multiply
函数也从调用栈移除。
-
任务队列(Task Queue):
- 任务队列用于存放异步任务的回调函数。当异步操作(如
setTimeout
、Promise
等)完成时,其回调函数不会立即进入调用栈执行,而是被放入任务队列。任务队列可以理解为一个先进先出(FIFO)的队列,新的回调函数不断被添加到队列末尾。
- 任务队列用于存放异步任务的回调函数。当异步操作(如
-
事件循环(Event Loop):
- 事件循环负责协调调用栈和任务队列。它不断地检查调用栈是否为空,如果调用栈为空,就从任务队列中取出一个任务(即回调函数),将其压入调用栈执行。这个过程会不断重复,这就是事件循环的工作机制。
处理异步任务时的表现举例
setTimeout
示例:
console.log('Start');
setTimeout(() => {
console.log('Timeout callback');
}, 0);
console.log('End');
- 首先,
console.log('Start')
被执行,打印Start
。 - 然后
setTimeout
函数被调用,它是一个异步操作,其回调函数并不会立即执行,而是被放入任务队列。 - 接着
console.log('End')
被执行,打印End
。 - 此时调用栈为空,事件循环开始工作,从任务队列中取出
setTimeout
的回调函数,压入调用栈执行,打印Timeout callback
。
Promise
示例:
console.log('Start');
Promise.resolve().then(() => {
console.log('Promise callback');
});
console.log('End');
console.log('Start')
执行,打印Start
。Promise.resolve()
返回一个已解决的Promise,其.then
回调函数被放入任务队列。console.log('End')
执行,打印End
。- 调用栈为空后,事件循环从任务队列取出Promise的
.then
回调函数压入调用栈执行,打印Promise callback
。需要注意的是,Promise的回调属于微任务,微任务队列的优先级高于宏任务队列(setTimeout
的回调属于宏任务),在事件循环的一次循环中,会先清空微任务队列,再处理宏任务队列。