LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

一张图,让你再也忘不了浏览器的事件循环(Event Loop)了

zhenglin
2025年12月5日 15:32 本文热度 131

前言

下面纯手工画了一张在浏览器执行JavaScript代码的Event Loop(事件循环) 流程图。

后文会演示几个例子,把示例代码放到这个流程图演示其执行流程。

当然,这只是简单的事件循环流程,不过,却能让我们快速掌握其原理。


概念

事件循环JavaScript为了处理单线程执行代码时,能异步地处理用户交互、网络请求等任务 (异步Web API),而设计的一套任务调度机制

它就像一个永不停止的循环,不断地检查(结合上图就是不断检查Task QueueMicrotask Queue这两个队列)并需要运行的代码。


为什么需要事件循环

JavaScript是单线程的,这意味着它只有一个主线程来执行代码。如果所有任务(比如一个耗时的计算、一个网络请求)都同步执行,那么浏览器就会被卡住,无法响应用户的点击、输入,直到这个任务完成。这会造成极差的用户体验。

事件循环就是为了解决这个问题而生的:它让耗时的操作(如网络请求、文件读取)在后台异步执行,等这些操作完成后,再通过回调的方式来执行相应的代码,从而不阻塞主线程



事件循环流程图用法演示

演示一:小菜一碟

先来一个都是同步代码的小菜,先了解一下前面画的流程图是怎样在调用栈当中执行JavaScript代码的。

console.log(1)


function funcOne() {

  console.log(2)

}


function funcTwo() {

  funcOne()

  console.log(3)

}


funcTwo()


console.log(4)

控制台输出:

1 2 3 4


下图为调用栈执行流程



每执行完一个同步任务会把该任务进行出栈。在这个例子当中每次在控制台输出一次,则进行一次出栈处理,直至全部代码执行完成。

演示二:小试牛刀

setTimeout+Promise组合拳,了解异步代码是如何进入任务队列等待执行的。

console.log(1)


setTimeout(() => {

  console.log('setTimeout', 2)

}, 0)


const promise = new Promise((resolve, reject) => {

  console.log('promise', 3)

  resolve(4)

})


setTimeout(() => {

  console.log('setTimeout', 5)

}, 10)


promise.then(res => {

  console.log('then', res)

})


console.log(6)

 

控制台输出:

1 promise 3 6 then 4 setTimeout 2 setTimeout 5


流程图执行-步骤一:

先执行同步代码,如遇到异步代码,则把异步回调事件放到后台监听对应的任务队列


  1. 执行console.log(1),控制台输出1

  2. 执行定时器,遇到异步代码,后台注册定时器回调事件,时间到了,把回调函数() => {console.log('setTimeout', 2)},放到宏任务队列等待。

  3. 执行创建Promise实例,并执行其中同步代码:执行console.log('promise', 3),控制台输出promise 3;执行resolve(4),此时Promise已经确定为完成fulfilled状态,把promise.then()的回调函数响应值设为4

  4. 执行定时器,遇到异步代码,后台注册定时器回调事件,时间未到,把回调函数() => { console.log('setTimeout', 5) }放到后台监听。

  5. 执行promise.then(res => { console.log('then', res) }),出栈走异步代码,把回调函数4 => { console.log('then', 4) }放入微任务队列等待。


流程图执行-步骤二:

上面已经把同步代码执行完成,并且把对应异步回调事件放到了指定任务队列,接下来开始事件循环


  • 扫描微任务队列,执行4 => { console.log('then', 4) }回调函数,控制台输出then 4

  • 微任务队列为空,扫描宏任务队列,执行() => {console.log('setTimeout', 2)}回调函数,控制台输出setTimeout 2

  • 每执行完一个宏任务,需要再次扫描微任务队列是否存在可执行任务(假设此时后台定时到了,则会把() => { console.log('setTimeout', 5) }加入到了宏任务队列末尾)。

  • 微任务队列为空,扫描宏任务队列,执行() => { console.log('setTimeout', 5) },控制台输出setTimeout 5



演示三:稍有难度

setTimeout+Promise组合拳+多层嵌套Promise

console.log(1)


setTimeout(() => {

  console.log('setTimeout', 10)

}, 0)


new Promise((resolve, reject) => {

  console.log(2)

  resolve(7)


  new Promise((resolve, reject) => {

    resolve(5)

  }).then(res => {

    console.log(res)


    new Promise((resolve, reject) => {

      resolve('嵌套第三层 Promise')

    }).then(res => {

      console.log(res)

    })

  })


  Promise.resolve(6).then(res => {

    console.log(res)

  })


}).then(res => {

  console.log(res)

})


new Promise((resolve, reject) => {

  console.log(3)


  Promise.resolve(8).then(res => {

    console.log(res)

  })


  resolve(9)

}).then(res => {

  console.log(res)

})


console.log(4)

上一个演示说明了流程图执行的详细步骤,下面就不多加赘叙了,直接看图!

talk is cheap, show me the chart

 

上图,调用栈同步代码执行完成,开始事件循环,先看微任务队列,发现不为空,按顺序执行微任务事件:

上图,已经把刚才排队的微任务队列全部清空了。但是在执行第一个微任务时,发现还有嵌套微任务,则把该任务放到微任务队列末尾,然后接着一起执行完所有新增任务


最后微任务清空后,接着执行宏任务。到此全部事件已执行完毕!

控制台完整输出顺序:

1 2 3 4 5 6 7 8 9 10

 


演示四:setTimeout伪定时

setTimeout并不是设置的定时到了就马上执行,而是把定时回调放在task queue任务队列当中进行等待,待主线程调用栈中的同步任务执行完成后空闲时才会执行。

代码高亮:

const startTime = Date.now()

setTimeout(() => {

  const endTime = Date.now()

  console.log('setTimeout cost time', endTime - startTime)

  // setTimeout cost time 2314

}, 100)


for (let i = 0; i < 300000; i++) {

  // 模拟执行耗时同步任务

  console.log(i)

}

控制台输出:

1 2 3 ··· 300000 setTimeout cost time 2314

下图演示了其执行流程:


演示五:fetch网络请求和setTimeout

获取网络数据,fetch回调函数属于微任务,优于setTimeout先执行。

setTimeout(() => {

  console.log('setTimeout', 2)

}, 510)


const startTime = Date.now()

fetch('http://localhost:3000/test').then(res => {

  const endTime = Date.now()

  console.log('fetch cost time', endTime - startTime)

  return res.json()

}).then(data => {

  console.log('data', data)

})


 
下图当前Call Stack执行栈执行完同步代码后,由于fetchsetTimeout都是宏任务,所以走宏任务Web API流程后注册这两个事件回调,等待定时到后了,由于定时回调是个普通的同步函数,所以放到宏任务队列;等待fetch拿到服务器响应数据后,由于fetch回调为一个Promise对象,所以放到微任务队列。


经过多番刷新网页测试,下图控制台打印展示了setTimeout延时为510msfetch请求响应同样是510ms的情况下,.then(data => { console.log('data', data) })先执行了,也是由于fetch基于Promise实现,所以其回调为微任务。


结语

这可能只是简单的JavaScript代码执行事件循环流程,目的也是让大家更直观理解其中原理。实际执行过程可能还会读取堆内存获取引用类型数据、操作dom的方法,可能还会触发页面的重排、重绘等过程、异步文件读取和写入操作、fetch发起网络请求,与服务器建立连接获取网络数据等情况。


但是
,它们异步执行的回调函数都会经过图中的这个事件循环过程,从而构成完整的浏览器事件循环。


参考文章:原文链接


该文章在 2025/12/5 15:32:50 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2025 ClickSun All Rights Reserved