浅谈事件循环Event Loop

前言

  1. 首先,JavaScript是一个单线程的脚本语言。

    JavaScript是单线程的,可以说这是JavaScript最核心也是最基本的特性。单线程就意味着同一时间只能干一件事

  2. 其次,JavaScript需要异步

    如果JS中不存在异步,只能自上而下执行,如果上一行解析时间很长,那么下面的代码就会被阻塞。对于用户而言,阻塞就意味着"卡死",这样就导致了很差的用户体验。

    但JavaScript虽然是单线程,但是完全可以模拟多线程(借助于浏览器这个宿主来实现),靠的就是 事件循环 Event Loop。所以JS中存在异步执行。

事件循环:

  • 是单线程程序在处理异步事件时进行的一种循环过程。

  • 对于异步事件,程序会先将其加入到事件队列中挂起,等主线程空闲时会去执行事件队列中的事件。这个过程会不断重复,形成一个循环。


事件循环机制

1. 执行栈

当JavaScript开始执行代码时,会按顺序逐行执行,并形成一个执行栈。

执行栈是一个后进先出(LIFO)的数据结构,用于存储函数调用。

在执行过程中如果遇到:

  • 同步任务:会按照代码顺序立即执行
  • 异步任务:不会立即执行,而是会将这些任务放入 Event Table(事件表)中注册并等待执行。同时,继续执行后续的同步任务,不会阻塞主线程的执行。

直到所有的同步任务执行完毕,即主线程(执行栈)空闲

2. 消息队列

异步任务在 Event Table 中注册函数,当异步任务的回调函数被触发时,Event Table 会将这些任务推入 Event Queue(事件队列)中等待执行。

异步任务又分为微任务和宏任务:

  • 微任务会被添加到微任务队列(MicroTask Queue)中,常见的微任务有:
    • Promise 的回调函数(即 thencatchfinally 中的函数)
    • MutationObserver API(用于监视DOM变更)
    • process.nextTick(Node.js 环境)
      .
  • 宏任务会被添加到宏任务队列(MacroTask Queue)中,常见的宏任务有:
    • <script>(整体代码)
    • setTimeoutsetInterval
    • I/O 操作(如Node.js中的文件读写)
      .

微任务和宏任务是分开存储在不同的队列中的,但它们都通过事件循环来调度和执行。

3. 事件循环

当所有的同步任务执行完毕,即主线程(执行栈)空闲时,事件循环(Event Loop)会检查 Event Queue(消息队列)中是否有待执行的异步任务。

如果有,事件循环会取出一个异步任务(通常是消息队列中的第一个任务)放入主线程执行栈中执行:

  • JavaScript引擎会先查看微任务队列是否有任务。如果有,就执行微任务队列中的所有任务,然后再继续查看微任务队列,直到微任务队列为空。

  • 执行完所有的微任务后,从宏任务队列中取出下一个宏任务,取出到执行栈中执行。

  • 这个过程会不断重复,从而形成“事件循环”。

在一个事件循环的迭代中,微任务总是先于宏任务执行,会先执行完所有的微任务,然后才会执行下一个宏任务。并且在一个宏任务执行完毕后,会立即检查并执行微任务队列中的所有任务。

执行的顺序是 执行栈中的代码 => 微任务 => 宏任务 => 微任务 => 宏任务 => ...

事件循环是 JavaScript 运行时不断监视执行栈和Event Queue(消息队列)的过程,负责协调同步任务和异步任务的执行

流程图

在这里插入图片描述

总结

综上所述,JavaScript 的事件循环机制负责协调同步任务和异步任务的执行。在主线程空闲时,事件循环会从消息队列中取出一个异步任务放入主线程中执行。在执行异步任务的过程中,可能会产生新的微任务,这些微任务会被放入微任务队列中等待处理。事件循环会优先处理微任务队列中的所有微任务,然后再处理消息队列中的一个消息。这样可以确保 JavaScript 代码的执行顺序符合预期。
在这里插入图片描述


举例

例1:

console.log(1)

setTimeout(function(){
    console.log(2)
},0)

console.log(3)

运行结果是: 1 3 2

它的执行顺序:

  1. console.log(1) 是同步任务,放入主线程里
  2. setTimeout() 是异步任务,被放入event table, 0 秒之后被推入event queue
  3. console.log(3) 是同步任务,放到主线程里
  4. 先执行完所有的同步任务,即先打印出 13
  5. 执行栈空闲时再去 event queue 里取出一个异步任务放入主线程执行栈中执行,即打印出 2

.

例2:

setTimeout(function(){
     console.log(1)
 });
 
 new Promise(function(resolve){
     console.log(2);
     resolve();
 }).then(function(){
     console.log(3)
 });
 
 console.log(4);

运行结果是: 2 4 3 1

也就是说 promise 在 setTimeout 之前执行了。

对例2的分析:

  1. 首先执行script下的宏任务,遇到setTimeout,将其放到宏任务的【队列】里
  2. 遇到 new Promise直接执行,打印 “2”
  3. 遇到then方法,是微任务,将其放到微任务的【队列里】
  4. 遇到console.log(4),同步任务,直接打印“4”
  5. 打印 “代码执行结束”
  6. 本轮宏任务执行完毕,查看本轮的微任务,发现有一个then方法里的函数, 打印"3"
  7. 到此,本轮的event loop全部完成。
  8. 下一轮的循环里,先执行一个宏任务,发现宏任务的【队列】里有一个setTimeout里的函数,执行打印"1"
  9. 所以最后的执行顺序是: 2 4 3 1

注意:

DOM事件也是基于Event Loop,但不是异步

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猫老板的豆

你的鼓励将是我创作的最大动力~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值