JavaScript中的Event Loop(事件循环)是什么?如何理解call stack的管理?
JavaScript调用栈与事件循环核心解析
一、什么是调用栈?
调用栈是JS引擎用来追踪函数执行顺序的栈结构,完全遵循「后进先出(LIFO)」的规则——最后被压入栈的函数,会最先被执行并弹出栈。
简单来说,每次你调用一个函数,JS就把这个函数“压”进栈里;函数执行完,就把它“弹”出去。栈里的任务必须一个个执行完,不会中途插队。
举个直观的代码例子:
function greet(name) { console.log(`Hello, ${name}`); } function sayHi() { greet('Alice'); } sayHi();
执行流程拆解:
- 执行
sayHi(),把sayHi压入调用栈 sayHi内部调用greet('Alice'),把greet压入栈(此时栈顶是greet)greet执行console.log,内置函数console.log被压入栈,执行后弹出greet执行完毕,弹出栈sayHi执行完毕,弹出栈,调用栈清空
调用栈的“阻塞”问题
因为JS是单线程,调用栈里的任务必须同步执行完才能处理下一个。如果遇到耗时的同步操作(比如百万级别的循环、复杂的计算),调用栈会一直被占用,主线程就会被“卡住”——浏览器没法响应用户点击、页面渲染也会暂停,直到栈里的任务执行完。
二、事件循环怎么和调用栈配合工作?
为了解决主线程阻塞的问题,JS引入了任务队列和事件循环机制,核心逻辑就是:把异步任务的回调放到队列里,等调用栈空了再处理。
具体运作步骤:
- JS引擎先执行调用栈里的所有同步任务,直到调用栈完全清空
- 事件循环开始工作,检查任务队列里有没有等待执行的回调函数
- 如果有,就把第一个回调函数压入调用栈执行;执行完后,再次检查调用栈是否为空,重复这个循环
- 如果队列里没有任务,事件循环就会进入等待状态,直到有新的异步任务加入队列
举个异步代码的例子:
console.log('开始'); setTimeout(() => { console.log('定时器回调'); }, 0); console.log('结束');
执行流程拆解:
console.log('开始')压入栈,执行后弹出,输出「开始」- 遇到
setTimeout,JS引擎不会等待倒计时,而是直接把它的回调函数放到任务队列里,继续执行后续同步代码 console.log('结束')压入栈,执行后弹出,输出「结束」- 调用栈清空,事件循环检测到任务队列里有回调,把它压入栈执行,输出「定时器回调」
补充:调用栈的细节注意点
- 每个被压入栈的函数,都会创建自己的执行上下文(包含变量、作用域链等信息),函数弹出栈时,对应的执行上下文也会被销毁
- 如果函数递归调用没有终止条件,会导致调用栈不断被压入新的函数,超过引擎允许的最大栈深度,抛出
Stack Overflow错误,比如:
function infiniteLoop() { infiniteLoop(); } infiniteLoop(); // Uncaught RangeError: Maximum call stack size exceeded
内容的提问来源于stack exchange,提问作者Snoopy
相关产品推荐
相关产品推荐

