是什么
JS 主线程只能一次干一件事,遇到要等的活(比如等网络、等定时器),总不能傻等着啥也不干。
事件循环就像个 “记事儿的”:先让主线程把不用等的活干完,再把 “等完要做的后续事” 排好队,等主线程一闲,就按顺序递给他干,这样既不耽误事,也不卡壳。
在javascript 中,所有的任务可以分成两种
- 同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行
- 异步任务:异步执行的任务,比如
ajax网络请求,setTimeout定时函数等
宏任务与微任务
微任务
一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前 常见的微任务有:
- Promise.then
- MutaionObserver
- Object.observe(已废弃;Proxy 对象替代)
- process.nextTick(Node.js)
宏任务
宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合
常见的宏任务有:
- script (可以理解为外层同步代码)
- setTimeout/setInterval
- UI rendering/UI事件
- postMessage、MessageChannel
- setImmediate、I/O(Node.js)
样例分析
console.log('start');
setTimeout(() => {
console.log('timeout1');
new Promise((resolve) => {
resolve();
}).then(() => {
console.log('then1');
});
}, 0);
new Promise((resolve) => {
console.log('promise1');
resolve();
}).then(() => {
console.log('then2');
setTimeout(() => {
console.log('timeout2');
}, 0);
});
console.log('end');
要求:写出最终打印顺序
解析步骤:
-
执行同步任务:
console.log('start')→ 打印start;- 遇到
setTimeout(timeout1),宏任务入队; - 遇到
new Promise,回调同步执行 → 打印promise1,resolve()标记微任务; console.log('end')→ 打印end;
同步任务结束,执行栈空。
-
清空微任务队列:
- 执行
Promise.then(then2)→ 打印then2; - then2 内部有
setTimeout(timeout2),宏任务入队(此时宏任务队列:timeout1、timeout2);
微任务清空,执行栈空。
- 执行
-
执行第一个宏任务(timeout1) :
- 打印
timeout1; - 内部
new Promise的then是微任务,入队;
当前宏任务执行完,立即清空微任务 → 打印then1;
微任务清空,执行栈空。
- 打印
-
执行第二个宏任务(timeout2) :
- 打印
timeout2。
- 打印
最终结果:start → promise1 → end → then2 → timeout1 → then1 → timeout2
总结
同步任务全执行 → 微任务全清空 → 执行一个宏任务 → 微任务全清空 → 执行下一个宏任务 → 循环直到所有任务完成。