浅谈事件循环Event Loop

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

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

其次,JavaScript需要异步

如果JS中不存在异步,只能自上而下执行,如果上一行解析时间很长,那么下面的代码就会被阻塞。对于用户而言,阻塞就意味着"卡死",这样就导致了很差的用户体验。但JavaScript虽然是单线程,但是完全可以模拟多线程(借助于浏览器这个宿主来实现),靠的就是Event Loop。所以JS中存在异步执行。
.

js中的内存分为 堆内存(heap)栈内存(stack)

  1. 堆内存中存的是我们声明的object类型的数据
  2. 栈内存中存的是 基本数据类型 以及 函数执行时的运行空间

我们的 同步 代码就放在执行栈中,那异步代码呢?浏览器会将 dom事件 ajax setTimeout等异步代码放到队列中,等待执行栈中的代码都执行完毕,才会执行队列中的代码,是不是有点像发布订阅模式。

这里将详细讲解JavaScript是如何通过事件循环(event loop)来实现异步执行


例1:

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

    console.log(3)

运行结果是: 1 3 2

也就是说,setTimeout里的函数并没有立即执行,而是延迟了一段时间,满足一定条件后,才去执行的,这类代码,我们叫异步代码。
所以,这里我们首先知道了JS里的一种分类方式,就是将任务分为: 同步任务和异步任务
.

按照这种分类方式,JS的执行机制是:

  1. 首先判断JS是同步还是异步,同步就进入主线程,异步就进入event table
  2. 异步任务在event table中注册函数,当满足触发条件后,被推入消息队列(event queue)
  3. 同步任务进入主线程后一直执行,直到主线程空闲时,才会去消息队列中查看是否有可执行的异步任务,如果有就推入主线程中

在这里插入图片描述

所以上面的例子,你是否可以描述它的执行顺序:

  1. console.log(1) 是同步任务,放入主线程里
  2. setTimeout() 是异步任务,被放入event table, 0 秒之后被推入event queue
  3. console.log(3)是同步任务,放到主线程里

当 1、3在控制条被打印后,主线程去消息队列event queue里查看是否有可执行的函数,执行setTimeout里的函数


例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 之前执行了。

异步任务又可以分为:

  1. microtask(微任务)
    当执行栈中的代码执行完毕,会在执行宏任务队列之前先看看微任务队列中有没有任务,如果有会先将微任务队列中的任务清空才会去执行宏任务队列
    包括:Promise,nextTick,callback,Object.observe,MutationObserver
  2. macrotask(宏任务)
    等待执行栈和微任务队列都执行完毕才会执行,并且在执行完每一个宏任务之后,会去看看微任务队列有没有新添加的任务,如果有,会先将微任务队列中的任务清空,才会继续执行下一个宏任务
    包括:script代码块,setTimeout,setInterval,I/O

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

异步任务的执行也是有先后顺序的:

  1. 执行一个宏任务,过程中如果遇到微任务,就将其放到微任务的【事件队列】里
  2. 当前宏任务执行完成后,会查看微任务的【事件队列】,并将里面全部的微任务依次执行完
    在这里插入图片描述

对例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,但不是异步

posted @ 2022-07-20 18:16  猫老板的豆  阅读(49)  评论(0编辑  收藏  举报