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
queueMicrotask
微任务队列(优先),在当前同步任务之后执行
宏任务
(Macrotask)
setTimeout
setInterval
setImmediate
requestAnimationFrame
DOM 事件(click, input, keydown, load, scroll 等用户交互事件)
宏任务队列,执行一次循环之后,排在下一个宏任务中执行

三、案例题目

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   // 第二个宏任务的微任务