JS中的Event Loop — 事件循环

2023/03/11

一、介绍

JavaScript 是单线程的,事件循环就是它用来“协调异步任务执行”的机制。这里还要介绍一下栈(Stack)和队列(Queue)。

1.Call Stack,调用堆栈

JS的Call Stack是一种经典的栈结构,它的特点是后进先出(last in first out,LIFO),下面例子可以解释:

function a() {
  b();
}
function b() {
  c();
}
function c() {
  console.log("Hello");
}

a();

调用顺序:
Call Stack:
→ c
→ b
→ a

2.Task Queue,任务队列

任务队列(Task Queue)是指在事件循环中等待执行的异步回调任务的集合,包含宏任务队列(Macrotask Queue)和微任务队列(Microtask Queue)。这些任务会在主线程空闲(同步代码执行完)后被依次执行,确保 JavaScript 单线程环境中的异步操作可以“有序排队”。

3.事件循环的执行流程

  • 执行主线程代码(立即执行同步任务
  • 检查异步任务队列(宏任务队列 / 微任务队列)
  • 取出任务执行
    • 当前宏任务执行完之后立即执行 微任务
    • 下一轮事件循环中执行 宏任务
  • 重复执行这个循环

二、常见任务类型

任务类型示例队列类型
同步任务普通代码主线程直接执行
微任务
(Microtask)
Promise.then
Promise.catch
Promise.finally
window.queueMicrotask 浏览器创建微任务API
await的后续代码包装为微任务
微任务队列(优先),在当前同步任务之后执行
宏任务
(Macrotask)
setTimeout 下一个事件循环
setInterval
setImmediate
requestAnimationFrame 浏览器执行动画API,下一帧渲染前执行回调
DOM 事件(click, input, keydown, load, scroll 等用户交互事件)
宏任务队列,执行一次循环之后,排在下一个宏任务中执行

三、await对事件循环的影响

当被 await 的 Promise 解决时,后续代码会作为微任务加入队列,等价于把后续代码放进Promise.then()中。

async function foo() {
  console.log(1);
  await bar();
  // 后续代码被包装为微任务
  // 等foo函数后的同步任务执行完才会执行微任务
  console.log(3);   
}

function bar() {
  console.log(2);
}

foo();
console.log(4);

// 输出顺序: 1 → 2 → 4 → 3

每个 await 都会创建新的微任务,需要注意在微任务队列中的层级关系。

// 使用 .then()
Promise.resolve()
  .then(() => console.log('then 1'))   // 第1层第1个微任务
  .then(() => console.log('then 2'));  // 第2层第1个微任务

// 使用 await
(async () => {
  await Promise.resolve();    // 下面三行包装为第1层第2个微任务
  console.log('await 1');     
  await Promise.resolve();    // 下面一行包装为第2层第2个微任务
  console.log('await 2');
})();

// 输出顺序:
// then 1 → await 1 → then 2 → await 2

四、案例题目

console.log("start")

setTimeout ( ()=>{
	console.log ('timer1')
	new Promise (function (resolve){
		console.log(" promise start ")
		resolve ();
	}).then (function () {
		console. log('promise1')
		// 注意:如果这里还有一个setimeout,那么输出将在promise2之后
	})
},0)

setTimeout (() =>{
	console.log( 'timer2')
	Promise.resolve( ).then(function() {
		console. log('promise2')
	})
},0)

console. log("end")

结果:
start  // 同步
end   // 同步
timer1   // 第一个宏任务中的同步任务 
promise start   // 第一个宏任务中的同步任务 new Promise(...)
promise1   // 第一个宏任务中的微任务
timer2    // 第二个宏任务的同步任务
promise2   // 第二个宏任务的微任务