【JavaScript基础知识】--事件循环(同步任务、异步任务、宏任务、微任务)

253 阅读4分钟

事件循环

JavaScript 的事件循环(Event Loop)是 JavaScript 运行时环境(如浏览器和 Node.js)用来处理异步代码和回调的一种机制。它允许 JavaScript 在执行长时间运行的操作(如 I/O、网络请求等)时不会阻塞用户界面或程序的其余部分。

事件循环的基本工作原理

  1. 调用栈(Call Stack) :JavaScript 代码执行时,会按照调用顺序将函数放入调用栈中。当函数执行完毕,它会被从栈中移除。
  2. Web APIs:浏览器提供了一系列的 Web APIs,如 setTimeoutfetchXMLHttpRequest 等,用于执行异步操作。这些 API 通常在“后台”线程中运行,以避免阻塞主线程。
  3. 任务队列(Task Queue) :当异步操作完成时,相应的回调函数会被添加到任务队列中。这个队列是先进先出(FIFO)的。
  4. 事件循环:当调用栈为空时,事件循环会从任务队列中取出任务,并将它们放入调用栈中执行。这个过程会不断重复,形成事件循环。
  5. 微任务队列(Microtask Queue) :除了任务队列外,JavaScript 还有一个微任务队列。微任务队列的优先级高于任务队列。在每次事件循环迭代结束时,JavaScript 引擎会先处理所有微任务队列中的任务,然后再处理任务队列中的任务。

JavaScript可以大致分为同步任务和异步任务

  • 同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行
  • 同步任务:异步执行的任务,比如ajax网络请求,setTimeout定时函数等
  • 60776c633a4c53179a514401fc69cc63.jpeg

    异步任务又分为宏任务与微任务 一般先执行宏任务再执行任务(先宏后微)

    宏任务

  • script(可以理解为外层同步代码)
  • setTimeout/setinterval
  • UI rendering/UI事件
  • postMessage、MessageChannel
  • setlmmediate、I/O、(Node.js)
  • 微任务

  • Promise.then
  • MutaionObserver
  • Object.observe(已废弃;Proxy对象)
  • process.nextTick(Node.js)
  • console.log(1);
    setTimeout(()=>{
        console.log(2);
    },0)
    new Promise((resolve,reject)=>{
        console.log('new Promise');
        resolve()
    }).then(()=>{
        console.log('then');
    })
    console.log(3);
     /**
    
    输出
    1
    new Promise
    3
    then
    2
    
    
    解析:
    1、经过 console.log(1); 所以打印   '1'
    2、定时器属于新的宏任务,等待上一个宏任务和微任务执行完后才会执行
    3、new Promise 直接执行  所以打印  '  new Promise   '
    4、.then 属于微任务,放入微任务队列,等待宏任务执行完后再执行
    5、经过 console.log(3); 所以打印   '3'
    6、执行玩宏任务=>执行微任务(即执行打印 'then')
    7、当第一次的宏任务和微任务执行完后执行下一次任务即执行定时器 打印  '2'
    **/
            
    

    async与 await

    async 和 await 是 JavaScript(ES2017 引入)中用于处理异步操作的关键字,它们让异步代码的编写和阅读变得更加直观和简单。在使用这些关键字之前,处理异步操作通常涉及到回调函数、Promises 或者生成器(Generators)。

    async

    • async 关键字用于声明一个异步函数,这意呀着该函数内部可以有异步操作。
    • 使用 async 声明的函数会隐式地返回一个 Promise。即使你没有在函数体中显式地返回一个 Promise,JavaScript 引擎也会将函数的返回值(无论是普通值还是其他类型的值)包装在一个 Promise 中返回。
    • async 函数中可以使用 await 关键字。

    await

    • await 关键字只能在 async 函数内部使用。
    • await 用于等待一个 Promise 完成,并返回其解决(resolved)的值。
    • 当执行到 await 表达式时,JavaScript 引擎会暂停当前 async 函数的执行,等待 Promise 完成(无论是解决还是拒绝),然后继续执行 async 函数并返回解决的值(如果 Promise 被解决)。如果 Promise 被拒绝,则会抛出一个错误,需要用 try...catch 来捕获。
    • await 可以让你以同步的方式写异步代码,提高了代码的可读性和可维护性。

    async函数返回一个promise对象,下面的两种方法是等效的

    function Fn(){
        return Promise.resolve('TEST');
    }
    async function asyncFn() {
        return 'TEST'
    }
    

    正常情况下,await命令后是一个Promise对象,返回该对象的结果。如果不是 Promise对象,就直接返回对应的值

    async function fff() {
        return await 123
    }
    fff().then(v => console.log(v))
    

    不管await后面跟着的是什么,await都会阻塞后面的代码

    
    async function awiatfn1() {
        console.log(1);
        await fn2()
        console.log(2); //阻塞 属于微任务等待宏任务执行完再执行
    }
    async function awaitfn2() {
        console.log('fn2');
    }
    awiatfn1()
    console.log(3);
     /*
    输出
    1
    fn2
    3
    2
    */
    

    综合流程分析案例

    async function  asyncfn1() {        //宏任务
        console.log('async1 start');   
        await asyncfn2()             
        console.log('async1 end');    //阻塞(后面执行) 属于微任务
    }
    
    async function asyncfn2() {       //宏任务
        console.log('async2');
    }
    console.log('script start');      //宏任务      
    setTimeout(function(){
        console.log('setTimeout');   //定时器下一个宏任务
    })
    asyncfn1()
    new Promise(function (resolve){      //宏任务
        console.log('promise1');
        resolve()
    }).then(function(){
        console.log('promise2');    //阻塞(后面执行)  属于微任务
    })
    console.log('script end');        //宏任务
    
    
    /*输出
    script start
    async1 start
    async2
    promise1
    script end
    async1 end
    promise2
    setTimeout
    
    解析:
    先执行宏任务再执行微任务
    1、先后执行的宏任务
    console.log('script start');=>asyncfn1();=>asyncfn2()=>new Promise=>console.log('script end');
    2、先后执行的微任务
    console.log('async1 end');=>console.log('promise2');
    3、下一个宏任务 
    console.log('setTimeout'); 
    */