• 前端面试之事件循环


    什么是事件循环

    首先, JavaScript是一门单线程的语言,意味着同一时间内只能做一件事,这并不意味着单线程就是阻塞,而是实现单线程非阻塞的方法就是事件循环

    在JavaScript中,所欲任务都可以分为:

    • 同步任务:立即执行的任务,同步任务一直会直接进入到主线程中执行
    • 异步任务:异步执行的任务,比如ajax网络请求,setTimeout定时任务等等
      在这里插入图片描述
      从上面可以看到,同步任务进入主线程,即主执行栈,异步任务进入任务队列,主线程内的任务执行完毕为空,会去任务队列中读取相应的任务,推入主线程执行。上面的过程不断重复就叫事件循环

    宏任务和微任务

    异步任务还可以细分为微任务和宏任务

    微任务

    一个需要异步执行的函数,执行时机主函数执行结束之后,当前宏任务执行之前

    常见的微任务有:

    • Promise.then
    • MutationObserver(监听指定DOM的变化)
    • Process.nextTick(Node.js)

    常见的宏任务有:

    • setTimeout/setInterval
    • postMessage、MessageChannel
    • UI rendering/UI事件(下轮事件循环执行之前)
    • Script(外层的同步代码)
    • setImmediate、I/O(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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    /**
     * 遇到console.log(1) 直接打印
     * setTimeout 是宏任务 放到宏任务队列里面
     * Promise 中的代码是直接打印的 所以执行console.log('new Promise')
     * then 是微任务 放到微任务队列中
     * console.log(3) 这直接打印
     * 开始执行异步任务
     * 首先执行微任务 then 中的代码 打印then
     * 执行完微任务执行宏任务setTimeout中的代码 打印2 
     **/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    打印的结果是:1 new Promise 3 then 2

    async 与 await

    async 是异步的意思,await可以理解为async wait,可以理解async就是用来声明一个异步方法,而await是用来等待异步方法执行

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

    function f() {
        return Promise.resolve('TEST');
    }
    
    // asyncF is equivalent to f!
    async function asyncF() {
        return 'TEST';
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

    async function f(){
        // 等同于
        // return 123
        return await 123
    }
    f().then(v => console.log(v)) // 123
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    async function fn1 (){
        console.log(1)
        await fn2()
        console.log(2) // 阻塞
    }
    
    async function fn2 (){
        console.log('fn2')
    }
    
    console.log(3)
    fn1()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    上面的例子中,await 会阻塞下面的代码运行,先执行async外面的同步代码,同步代码执行完成后,再回到async函数中执行await之后的代码,也就是阻塞的代码

    所以上述输出结果为:3 1 fn2 2

    流程分析

    通过对上面的了解,我们对JavaScript的各个场景的执行顺序有了大致的了解

    请看以下的代码:

    async function async1() {
        console.log('async1 start')
        await async2()
        console.log('async1 end')
    }
    async function async2() {
        console.log('async2')
    }
    console.log('script start')
    setTimeout(function () {
        console.log('settimeout')
    })
    async1()
    new Promise(function (resolve) {
        console.log('promise1')
        resolve()
    }).then(function () {
        console.log('promise2')
    })
    console.log('script end')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    结果是:script start -> async1 start -> async2 -> promise1 -> script end -> async1 end -> promise2 -> settimeout

    分析过程

    /**
     * 1.遇到async1,async2函数定义不用执行,
     * 2.执行 console.log('script start') -> 输出:script start
     * 3.setTimeout 是宏任务 放到宏任务里面
     * 4.执行async1()
     * 5.进入执行async1 里执行 console.log('async1 start')  -> 输出:async1 start
     * 6.遇到await 执行 async2,然后阻塞await后面的代码
     * 7.进入执行async2 里执行 console.log('async2')  -> 输出:async2
     * 8.遇到Promise 执行 console.log('promise1') -> 输出:promise1
     * 9.执行console.log('script end') -> 输出:script end
     * 10.开始执行异步任务
     * 11.执行微任务await后面的代码 console.log('async1 end')  -> 输出:async1 end
     * 12.执行微任务then里面的代码 console.log('promise2')  -> 输出:promise2
     * 13.执行宏任务setTimeout里面的代码 console.log('settimeout')  -> 输出:settimeout
    **/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
  • 相关阅读:
    如何解决msvcr100.dll丢失问题?5个实用的解决方法分享
    【复现】蓝凌OA SQL注入漏洞_61
    The significance of void 0 in JS
    手写RPC——数据序列化工具protobuf
    从零开始的C++(五)
    【总结】maven 打包刷新下载依赖卡死
    Centos7,yum安装mysql
    创建js对象的几种方式
    【Py】使用flask-apscheduler动态调整作业参数(附源码)
    嵌入式Qt-做一个秒表
  • 原文地址:https://blog.csdn.net/qq_43706089/article/details/134372343