
文章目录 4
面试题:JavaScript 事件循环
下面代码的输出顺序是什么?请解释原因。
console.log('1');
setTimeout(() => {
console.log('2');
}, 0);
Promise.resolve()
.then(() => {
console.log('3');
})
.then(() => {
console.log('4');
});
console.log('5');回答时说明三个点即可:
最终输出顺序;
同步任务、微任务和宏任务分别有哪些;
为什么
setTimeout(..., 0)不是立即执行。
我的回答
1 5 2 3 4
同步任务:
console.log('1');
console.log('5');宏任务:
console.log('1');
console.log('5');
Promise.resolve()
.then(() => {
console.log('3');
})
.then(() => {
console.log('4');
});微任务:
setTimeout(() => {
console.log('2');
}, 0);因为setTimeout(..., 0)它是异步任务,属于微任务序列
点评
正确输出顺序是:
1
5
3
4
2定时器任务不属于微任务,setTimeout 的回调属于宏任务。
执行过程:
执行同步代码,输出
1。setTimeout注册一个宏任务。第一个
.then()注册一个微任务。继续执行同步代码,输出
5。同步代码结束,清空微任务队列:
第一个
.then()输出3。它执行完成后,第二个
.then()进入微任务队列,随后输出4。
微任务队列清空后,执行定时器宏任务,输出
2。
可以记为:
当前同步代码
→ 清空所有微任务
→ 执行下一个宏任务
→ 再次清空微任务常见分类:
类型 | 示例 |
|---|---|
同步任务 | 普通函数、 |
微任务 |
|
宏任务 |
|
setTimeout(fn, 0) 中的 0 只是表示达到最短延迟后可以进入可执行状态,不代表马上执行。
为什么回再次清空微任务
因为事件循环的规则不是“只执行一次微任务”,而是:
每完成一个宏任务,就持续清空微任务队列,直到队列为空,再执行下一个宏任务。
在这段代码中:
Promise.resolve()
.then(() => {
console.log('3');
})
.then(() => {
console.log('4');
});执行过程如下:
同步代码结束
微任务队列:[输出 3]
执行第一个 then
→ 输出 3
→ 第一个 then 返回的 Promise 完成
→ 第二个 then 被加入微任务队列
微任务队列:[输出 4]
继续清空微任务队列
→ 输出 4
微任务队列为空
→ 执行 setTimeout
→ 输出 2所以这里并不是离开微任务阶段后“再次进入”,而是在同一次微任务检查中,继续执行新加入的微任务。
简化规则:
执行一个宏任务
→ 清空微任务,包括期间新增的微任务
→ 浏览器可能进行渲染
→ 执行下一个宏任务这样设计可以让 Promise 的连续处理尽快完成。不过如果微任务不断产生新的微任务,宏任务和页面渲染就可能长期得不到执行,这叫做微任务饥饿:
function loop() {
queueMicrotask(loop);
}
loop();
setTimeout(() => {
console.log('可能一直无法执行');
}, 0);相关文章
- 面试题-基础
Vue 3 响应式解构:为什么替换 user 后出现“李四 / 王五”
通过一道 Vue 3 代码题,区分 reactive 对象解构、嵌套属性修改与对象替换的行为,并用 toRef 保持源属性关联。文章保留首次作答及追问补答,说明从误判到纠正的过程。
- 面试题-基础
Vue 3 编辑弹窗:草稿状态、取消重开与父子数据契约
以一个始终挂载的姓名编辑弹窗为场景,给出覆盖数据归属、保存取消、重开初始化和外部更新冲突的完整 Vue 3 解法。结合首次回答与递进补答,说明本地草稿、脏标记和版本校验各自解决的问题。
- 面试题-基础
JavaScript 浅拷贝、深拷贝与 structuredClone:前端状态更新的正确方式
通过一道对象复制代码题,梳理 JavaScript 浅拷贝、JSON 深拷贝、structuredClone 和 transferable object 的边界,并解释 React 结构共享与 Vue 3 响应式更新的差异。
评论
0 条评论会在审核后展示,邮箱不会公开。