JS异步进阶Event Loop以及解题思路

112 阅读3分钟

一、event loop (事件循环)

  1. JS是单线程运行的
  2. 异步要基于回调来实现
  3. event loop就是异步回调的实现原理

JS执行顺序?

从前到后,一行行执行,如果某行执行报错,停止下面代码的执行,先同步后异步

Event loop过程

Call Stack、Web APIs、CallBack Queue(回调队列)、Event Loop

无标题.png

    //1.同步任务先执行,放到Call Stack 中,先打印
    console.log(1)
    //2.下一行,遇到异步任务,先放到Web APIs中,等待
    setTimeout(function fn() {
      console.log(3)
    });
    //3.同步任务,继续执行完成,开始运行event loop, 2操作中的方法时间一到,先放到callBack Queue,等待event loop轮询,找到就放Call Stack执行
    console.log(2)
  1. 同步代码,一行一行放在 Call Stack执行。
  2. 遇到异步,会先‘纪录’下(放在web APIs)待时机(定时、网络请求等)
  3. 时机到了,就移动到Callback Queue
  4. 如果Call Stack为空(即同步代码执行完成)Event Loop开始工作,轮询查找 callBack Queue
  5. 如果则移动到Call Stack执行
  6. 然后继续轮询查找(永动机一样)
DOM事件和event loop的关系:

JS是单线程的,异步(SetTimeout,ajax等)使用回调,基于event loop,DOM事件也使用了回调,基于event loop(操作事件时触发调用即可)

DOM渲染和event loop的关系:

JS单线程的,而且和DOM渲染公用一个线程,需要留时机给DOM渲染

每次Call Stack清空(即每次轮询结束),即同步任务执行完,都是DOM重新渲染的机会,DOM结构如有改变则重新渲染,然后再去触发下一次Event loop

二、宏任务(macroTask)和微任务(microTask)

  1. 宏任务:setTimeout,setInterval,ajax,DOM事件(DOM渲染后触发)
  2. 微任务:Promise.then async/await(DOM渲染前触发)
  3. 微任务执行时机要比宏任务要早

过程和区别

  1. 微任务:进入Micro task Queue(微任务回调队列)
  2. 宏任务:进入CallBack Queue(回调队列)
  3. 区别:微任务是ES6语法规定的,宏任务是由浏览器规定的

三、最终总结一下我自己的解题思路

  1. 主流程 :先查找异步任务先放到异步队列,再从头走同步任务,遇到异步任务都把任务放到异步队列,等待执行。
  2. 同步任务(Call Stack执行),遇到微任务按顺序先放到Micro task Queue(微任务回调队列),等到执行。
  3. 同步任务执行完成,先执行Micro task Queue的微任务,微任务执行完后才能进行下一步。
  4. 最后一步,Event Loop会去获取,异步队列中的任务,继续循环进行(Call Stack执行)。

几个注意的点:

  1. Promise 默认返回值为resolve()
   new Promise((resolve)=> {
    console.log(6);
    resolve();
    }).then(()=>{
        console.log(7)
    }).then(()=>{
        console.log(8)
        //return Promise.reject("异常") 存在这句话才会打印出来9,否则默认执行成功,结果6 7 8
    }).catch(()=>{
        console.log(9)
    })
  1. async/await(注意:同步运行async方法先跳过,调用在执行)
    async function async1() {
        await async2()
        console.log(2)
    }
    async function async2() {
        console.log(3)
    }
    async1()
    
    //上述代码等价于
    new Promise(resolve => {
       console.log(3)
       resolve()
    }).then(res=> {
        console.log(2)
    })
简单例子
    console.log(1)//2.同步 先打印

    async function async1() {//3.anync 方法 先跳出
        await async2()//6.先执行async2,后续代码等待(Promise.then为微任务)
        console.log(2)//8.微任务1排序,继续主流程
        setTimeout(function() {//9.异步任务2放到异步队列
          console.log(9)
        },0)
        
    }
    async function async2() {//4.anync 方法 先跳出
        console.log(3)//7.先打印,回到async1(),微任务1排序
    }
    async1()//5.anync方法调用,回到async1()
    setTimeout(function() { //9.异步任务1先到异步队列
      new Promise(function(resolve) {
        console.log(4);
        resolve();
        }).then(function() {
            console.log(5)
        })
    }, 0)
    new Promise(function(resolve) {
        console.log(6);//10.Promise示例同步任务先打印
        resolve();
    }).then(function() {
        console.log(7) //11.微任务2排序
    }).then(function() {
        console.log(8)//13.微任务3排序
    })
    
    //结果:同步任务==》微任务==》异步任务(1,3,6,2,7,8,4,5,9)
    //同步任务(1,3,6)
    //微任务(2,7,8)
    //异步任务(4,5,9)