• javaScript 中的宏任务、微任务


    宏任务:

    是指,需要排队等待 JavaScript 引擎空闲时才能执行的任务,

    常见的宏任务包括 setTimeout、setInterval、setImmediate(Node.js 独有)、requestAnimationFrame、I/O 操作、XMLHttpRequest、DOM事件等

    微任务:

    是指,在当前任务执行结束后,立即执行的任务,它可以看作是在当前任务的“尾巴”添加的任务,

    常见的微任务包括 Promise回调函数、process.nextTick、Object.observe(已废弃)、MutationObserver

    说一下

    JavaScript引擎,会先执行当前任务中的所有微任务,然后再执行宏任务队列中的第一个任务,这个过程会不断重复,直到宏任务队列中的任务被全部执行完毕。

    宏任务、微任务都属于异步任务,

    宏任务包括 setTimeout、setInterval、I/O 等操作,

    微任务包括 promise的then、resolve、reject 等

    首先执行同步代码,遇到异步任务,如果是宏任务就放入宏任务队列,微任务就放到微任务队列,

    当同步代码执行完毕,就会执行微任务队列,直到微任务队列清空,

    然后从宏任务对列调用宏任务到主线程执行,就这样不断循环,直到所有任务执行完毕




    JavaScript之所以要区分微任务、宏任务,是因为,微任务和宏任务的执行顺序不同,这对Web开发中一些异步操作的实现有重要的影响

    在JavaScript中,微任务会优先于宏任务执行,
    这意味着,在当前任务执行结束后,所有微任务都会被立即执行,而宏任务只有在所有微任务执行完毕后才会执行,
    这种执行顺序保证了微任务的优先级,可以避免一些问题的出现。

    1、
    比如,处理 Promise对象时可能会出现的竞态条件,

    举个例子,当我们使用Promise对象时,它会返回一个Promise实例并将回调函数放入微任务队列中,

    当 Promise的状态发生改变时,它会立即执行微任务队列中的回调函数,而不是等待当前任务结束后再执行,

    这种特性可以保证 Promise回调函数的执行顺序,避免出现竞态条件,从而使代码更加可靠。

    补充(举例解释一下):
    假设,有两个Promise对象P1、P2,
    它们的状态都发生了改变,但是P1的回调函数在微任务队列中先于P2的回调函数执行,
    这种情况下,即使P2的状态改变发生在P1之前,P2的回调函数也会等待P1的回调函数执行完毕后再执行,
    这样可以避免多个回调函数同时执行而产生的竞态条件。

    如果当前任务执行一半了,Promise状态发生改变了,会停下当前任务去执行微任务队列中Promise的回调函数吗??????
    不会的,
    即使当前任务执行了一半,如果Promise状态发生改变,也会立即执行微任务队列中的回调函数,
    但是不会停下当前任务的执行。当前任务的执行会继续完成,然后才会回到微任务队列中执行其他的回调函数,
    因此,即使Promise状态发生改变,也不会直接打断当前任务的执行。

    2、
    另一方面,宏任务的执行是在当前任务结束后才会执行的,这意味着,可以将一些耗时的操作放入宏任务队列中,从而避免阻塞当前任务的执行,

    比如,我们可以将一些需要等待一段时间才能执行的代码放入 setTimeout 的回调函数中,

    这样可以使页面在执行这些代码的同时仍然保持响应,提高用户体验。

    因此,JavaScript 之所以要区分微任务和宏任务,是为了保证异步操作的正确性和性能。


    JS中微任务和宏任务执行顺序

    1、首先执行当前代码(同步任务),直到遇到第一个宏任务或微任务,

    2、如果遇到微任务,则将它添加到微任务队列中,继续执行同步任务,

    3、如果遇到宏任务,则将它添加到宏任务队列中,继续执行同步任务,

    4、当前任务执行完毕后,JavaScript引擎会先执行所有微任务队列中的任务,直到微任务队列为空,

    5、然后执行宏任务队列中的第一个任务,直到宏任务队列为空,

    重复步骤4、步骤5,直到所有任务都被执行完毕。

    需要注意的是,微任务比宏任务优先级要高,因此在同一个任务中,如果既有微任务又有宏任务,那么微任务会先执行完毕,

    而在不同的任务中,宏任务的执行优先级要高于微任务,因此在一个宏任务执行完毕后,它才会执行下一个宏任务和微任务队列中的任。

    • 举个例子,

    假设当前代码中有一个 setTimeout(宏任务) 和一个 Promise(微任务),它们分别对应一个宏任务和一个微任务。那么执行顺序如下:

    1、执行当前代码,将 setTimeout 和 Promise 添加到宏任务和微任务队列中,

    2、当前任务执行完毕,JavaScript引擎,先执行微任务队列中的 Promise回调函数,

    3、微任务队列为空后,再执行宏任务队列中的 setTimeout回调函数。

    需要注意的是:在一些特殊情况下,微任务和宏任务的执行顺序可能会发生变化,比如在使用 MutationObserver 监听 DOM 变化时,
    它会被视为一个微任务,但是它的执行顺序可能会比其他微任务更靠后。因此,需要根据具体情况来理解和处理微任务和宏任务的执行顺序。


    看下面几个案例

    1、微任务:Promise回调函数,宏任务:setTimeout回调函数

    console.log('start')
    
    setTimeout(() => console.log('setTimeout'), 0)
    
    Promise.resolve()
    	.then(() => console.log('Promise'))
    	
    console.log('end')
    
    // start、end、Promise、setTimeout
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    首先输出 start,然后通过 setTimeout 方法注册了一个回调函数,它会被添加到宏任务队列中,

    接着创建了一个 Promise实例,并且通过 then方法注册了一个回调函数,在 Promise对象的状态改变时会执行这个回调函数,

    接着输出 end,

    因为 Promise回调函数是微任务,所以它会被添加到微任务队列中,等待执行,

    等到主线程的同步任务执行完毕后,JavaScript引擎会先执行微任务队列中的任务,输出 Promise,然后执行宏任务队列中的任务,输出 setTimeout。


    2、微任务:process.nextTick回调函数,宏任务setImmediate 回调函数

    console.log('start')
    
    setImmediate(() => console.log('setImmediate'))
    
    process.nextTick(() => console.log('process.nextTick'))
    
    console.log('end')
    
    // start、end、process.nextTick、setImmediate
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在Node.js环境中,process.nextTick回调函数是排在微任务队列最前面的,优先级比 Promise回调函数还要高,
    而 setImmediate回调函数是排在宏任务队列最后面的。

    所以,以上代码会先输出 start,然后输出 end,
    等到主线程的同步任务执行完毕后,JavaScript引擎会先执行微任务队列中的任务,输出 process.nextTick,然后执行宏任务队列中的任务,输出 setImmediate。


    3、微任务:Promise回调函数,宏任务:requestAnimationFrame回调函数

    console.log('start')
    
    requestAnimationFrame(() => console.log('requestAnimationFrame'))
    
    Promise.resolve()
    	.then(() => console.log('Promise'))
    	
    console.log('end')
    
    // start、end、Promise、requestAnimationFrame
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    首先输出 start,然后通过 requestAnimationFrame方法注册了一个回调函数,它会被添加到宏任务队列中,

    接着创建了一个 Promise实例,并且通过 then方法注册了一个回调函数,在 Promise对象的状态改变时会执行这个回调函数,

    接着输出 end,

    因为 Promise回调函数是微任务,所以它会被添加到微任务队列中,等待执行,

    等到主线程的同步任务执行完毕后,JavaScript引擎会先执行微任务队列中的任务,输出 Promise,然后执行宏任务队列中的任务,输出 requestAnimationFrame。


    4、微任务:Promise回调函数,宏任务:XMLHttpRequest

    console.log('start')
    
    // 创建 XMLHttpRequest对象并设置 onload回调函数。这个函数会在 HTTP请求成功完成后被调用。
    const xhr = new XMLHttpRequest()
    xhr.onload = () => console.log('XMLHttpRequest')
    xhr.open('GET', 'someurl')
    xhr.send()
    
    Promise.resolve()
      .then(() => console.log('Promise'))
    
    console.log('end')
    
    // start、end、Promise、XMLHttpRequest
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    首先输出 start,然后创建了一个 XMLHttpRequest对象,并且通过 open方法 和 send方法发送了一个 GET请求,

    接着通过 onload方法注册了一个回调函数,在请求成功后会执行这个回调函数,

    接着创建了一个 Promise实例,并且通过 then方法注册了一个回调函数,在 Promise对象的状态改变时会执行这个回调函数,

    接着输出 end,

    因为 Promise回调函数是微任务,所以它会被添加到微任务队列中,等待执行,

    等到主线程的同步任务执行完毕后,JavaScript引擎会先执行微任务队列中的任务,输出 Promise,

    然后等待 XMLHttpRequest对象的回调函数执行,

    当请求成功后,JavaScript引擎会执行宏任务队列中的任务,输出 XMLHttpRequest。

    • 请注意,这个执行顺序取决于网络请求的速度和主线程的同步任务执行速度。如果网络请求速度较慢,或者主线程的同步任务执行速度较慢,那么 Promise 和 XMLHttpRequest 的执行顺序可能会稍有不同。
  • 相关阅读:
    笔试题 键盘 车队
    java毕业设计毕业生实习管理系统Mybatis+系统+数据库+调试部署
    字符函数和字符串函数详解
    D2--FPGA SPI接口通信2022-08-03
    css 设置border边框颜色渐变效果
    运维SRE-14 自动化批量管理
    一文玩转RabbitMQ
    【MySQL篇】授权:授权与回收
    Mysql 的账户管理,索引,存储引擎
    动手学深度学习(Pytorch版)代码实践 -卷积神经网络-29残差网络ResNet
  • 原文地址:https://blog.csdn.net/pig_ning/article/details/134437019