事件循环的主要步骤:
执行栈(Call Stack):
同步代码直接进入栈中依次执行。
任务队列(Task Queue):
异步任务(如 setTimeout、DOM 事件、Ajax 回调)完成后将其回调函数放入队列,等待被主线程处理。
微任务队列(Microtask Queue):
微任务(如 Promise 的 then 回调)优先于任务队列执行。
事件循环(Event Loop):
每次循环从栈中取出同步代码执行。
如果栈为空,则检查微任务队列,执行其中所有任务。
微任务完成后,处理任务队列中的任务。
上案例
console.log('Start');
function foo() {console.log("Inside foo");
}
function bar() {foo();console.log("Inside bar");
}
setTimeout(() => {console.log('Timeout');
}, 0);Promise.resolve().then(() => {console.log('Promise');
});
bar();console.log('End');
案例分析
分析任务分类
同步任务(立即执行):
console.log(‘Start’);:同步任务,立即输出。
bar();:同步任务,调用后执行 foo() 和 console.log(‘Inside bar’);。
console.log(‘End’);:同步任务,立即输出。
微任务(优先于宏任务):
Promise.resolve().then(…):创建了一个微任务,将回调 () => { console.log(‘Promise’); } 放入 微任务队列。
宏任务:
setTimeout(…):创建了一个宏任务,将回调 () => { console.log(‘Timeout’); } 放入 宏任务队列。
执行过程
同步代码先执行,微任务队列的任务优先执行,然后再处理宏任务队列。
执行同步任务
console.log(‘Start’)
输出:Start
(同步代码,立即执行)
bar()
进入 bar():
foo()
进入 foo():
console.log(“Inside foo”)
输出:Inside foo
(同步代码,立即执行)
foo() 执行完毕,返回。
console.log(“Inside bar”)
输出:Inside bar
(同步代码,立即执行)
bar() 执行完毕,返回。
console.log(‘End’)
输出:End
(同步代码,立即执行)
执行微任务队列
Promise.resolve().then(…)
回调 () => { console.log(‘Promise’); } 执行:
输出:Promise
(微任务优先于宏任务)
执行宏任务队列
setTimeout(…)
回调 () => { console.log(‘Timeout’); } 执行:
输出:Timeout
(宏任务最后执行)
最终输出结果
Start
Inside foo
Inside bar
End
Promise
Timeout
关于事件循环的疑问:
事件循环的提到了执行栈,可栈不是先进的后出吗?如果把同步代码依次放到栈里面,不应该代码顺序会颠倒过来吗
调用栈的工作原理
调用栈是一个按需管理的栈结构,同步代码的执行过程是:压入栈,执行完毕,弹出栈。这遵循了 JavaScript 的单线程运行机制。
以下是调用栈在执行同步代码时的逐步行为:
遇到代码行时,会将该行的执行上下文压入栈中。
执行上下文完成(执行完这行代码),对应的栈帧就从栈中弹出。
重复上述过程,代码顺序因此得以保持。