菜鸟学习路程-宏任务和微任务

760 阅读8分钟
  • 事件循环:同步和异步任务分别进入不同的执行环境,同步的进入主线程,即主执行栈,异步的进入任务队列。主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。 上述过程的不断重复就是我们说的 Event Loop (事件循环)。

      setTimeout(_ => console.log(4))
      new Promise(resolve => {
      resolve()
      console.log(1)
      }).then(_ => {
      console.log(3)
      })
    
      console.log(2)
      解析:
      整体script作为第一个宏任务开始执行: 遇见第一个setTimeout 将其放入第二个宏任务队列 
      执行new Peromise() resolve().then将其放入 第一个微任务队列 继续执行 打印 1
      打印 2 第一个宏任务执行完毕  开始执行第一个宏任务队列里面的第一个微任务 打印3 第一个宏任务队列里面的微任务执行完毕 
      开始执行第二个宏任务 打印4
    

new Promise在实例化的过程中所执行的代码都是同步进行的,而then中注册的回调才是异步执行的。同一次事件循环中,微任务永远在宏任务之前执行。

  • 宏任务:(浏览器定义的)

    • 宏任务中可以创建微任务,但是在宏任务中创建的微任务不会影响当前宏任务的执行。(将微任务 放置到当前宏任务队列的 微任务队列里面)

    • 当一个宏任务队列中的任务全部执行完后,会查看是否有微任务队列,如果有就会优先执行微任务队列中的所有任务,如果没有就查看是否有宏任务队列

      # 浏览器 NODE
      /O true fasle
      setTimeout true true
      setInterval true true
      requestAnimationFrame(这个是用于数据渲染) true true
      setImmediate false true
    • 宏任务中可以创建微任务,但是在宏任务中创建的微任务不会影响当前宏任务的执行。

    • 可以有多个宏任务

  • 微任务:(js引擎定义 因为并不是挂载到window上面的)

    • 在上一个宏任务队列执行完毕后,如果有微任务队列就会执行微任务队列中的所有任务
    • new promise((resolve)=>{ 这里的函数在当前队列直接执行 }).then( 这里的函数放在微任务队列中执行 )
    • 微任务队列上创建的微任务,仍会阻碍后方将要执行的宏任务队列(也就是会在当前微任务队列里 执行完毕)
    • 由微任务创建的宏任务,会被丢在异步宏任务队列中执行(会被放到 宏任务队列里面 也就是最后一个 如果当前宏任务是队列是1 那么被创建的宏任务 就是队列2 需要等到当前队列1的微任务执行完毕以后 才会执行 )
  • 举例:

    • 在微任务中创建微任务

         setTimeout(_ => console.log(4))
         new Promise(resolve => {
         resolve()
         console.log(1)
         }).then(_ => {
         console.log(3)
         Promise.resolve().then(_ => {
         	console.log('before timeout')
         }).then(_ => {
         	Promise.resolve().then(_ => {
         	console.log('also before timeout')
         	})
         })
         })
      
         console.log(2);
         解析:
         第一次加载是第一个宏任务: 将setTimeout 放入第二个宏任务队列 继续执行 new Promise resolve() 将其then 放入第一个 宏任务队列里面的 第一个微任务队列 继续执行 打印1 继续打印 2 第一个宏任务队列执行完毕 
         开始执行第1个微任务 打印3 将第二个Promise.resolve().then放入 第一个宏队列的第2个微任务 继续执行第一个微任务的代码 
         以此类推 然后打印 before timeout also before timeout 现在第一个宏队列的微任务队列全部执行完毕 开始执行第二个宏队列 打印4
      
    • 宏任务中创建微任务

        // 宏任务队列 1
        setTimeout(() => {
        // 宏任务队列 
        console.log('timer_1');
        setTimeout(() => {
        	// 宏任务队列 3
        	console.log('timer_3')
        }, 0)
        new Promise(resolve => {
        	resolve()
        	console.log('new promise')
        }).then(() => {
        	// 微任务队列 1
        	console.log('promise then')
        })
        }, 0)
      
        setTimeout(() => {
        // 宏任务队列 2
        console.log('timer_2')
        }, 0)
      
        console.log('========== Sync queue ==========')
        解析:
        第一个宏任务是 整个scrpit 遇见第一个定时器 放入 第二个宏任务 继续执行 遇见第二个定时器 放入第三个宏任务 继续执行 输出========== Sync queue ========== 第一个宏任务执行完毕 又没有第一个宏任务的微任务 
        执行第二个宏任务 打印 timer_1 继续执行 将第三个定时器放入 第4个宏任务队列 继续执行new Promise 将then 放入第二个宏任务的第1个微任务 继续执行 打印console.log('new promise')  执行完毕 开始执行第二个宏任务的第一个微任务 console.log('promise then') 执行完毕
        执行第三个宏任务 console.log('timer_2') 
        执行第4个 console.log('timer_3')
      
  • 总结: **微任务队列优先于宏任务队列执行,微任务队列上创建的宏任务会被后添加到当前宏任务队列的尾端,微任务队列中创建的微任务会被添加到微任务队列的尾端。只要微任务队列中还有任务,宏任务队列就只会等待微任务队列执行完毕后再执行。**

  • 对于包含async和await的异步执行

    • 一旦遇到await 就立刻让出线程,阻塞后面的代码等候之后,对于await来说分两种情况

      • 不是promise 对象:
        • 如果await 后面直接跟的为一个变量,比如:await 1这种情况的话相当于直接把await后面的代码注册为一个微任务,可以简单理解为promise.then(await下面的代码)。然后跳出async1函数,执行其他代码,当遇到promise函数的时候,会注册promise.then()函数到微任务队列,注意此时微任务队列里面已经存在await后面的微任务。所以这种情况会先执行await后面的代码(async1 end),再执行async1函数后面注册的微任务代码(promise1,promise2)。 (举例demo1)
      • 是promise对象 (异步函数调用)

      如果不是promise,await会阻塞后面的代码,先执行async外面的同步代码,同步代码执行完毕后,在回到async内部,把promise的东西,作为await表达式的结果 如果它等到的是一个 promise 对象,await 也会暂停async后面的代码,先执行async外面的同步代码,等着 Promise 对象 fulfilled,然后把 resolve 的参数作为 await 表达式的运算结果。

      demo1:
      async function async1() {
      console.log( 'async1 start' )
      await async2() //相当于是柱塞后面的代码 放入当前宏任务队列里面的第一个微任务队列
      console.log( 'async1 end' )
      }
      async function async2() {
      	console.log( 'async2' )
      }
      async1()
      console.log( 'script start' );
      输出:
      async1 start -> async2 -> script start ->  async1 end
      

      async function async1() {
          console.log( 'async1 start' ) ---> 第一个输出
          await async2() ---> 执行函数 函数执行完毕 返回到这里 然后退出 async1函数
          console.log( 'async1 end' ) ---> 第5个输出 第一个宏任务的同步代码执行完毕 准备执行第一个宏任务的微任务
      }
      async function async2() {
          console.log( 'async2' )  ---> 第二个输出
      }
      console.log( 'script start' ) 
      setTimeout( function () {
          console.log( 'setTimeout' )  ---> 第二个宏任务
      }, 0 )
      async1(); ----> 第一个执行
      new Promise( function ( resolve ) {
          console.log( 'promise1' )  ---> 执行同步函数 第三个输出 执行完毕后 就返回到async1中继续执行
          resolve();
      } ).then( function () {
          console.log( 'promise2' )   ---> 第六个输出
      } )
      console.log( 'script end' ) ---> 第4个输出
      

      await会阻塞后面的代码,先执行async外面的同步代码,同步代码执行完毕后,在回到async内部,把promise的东西,作为await表达式的结果

      在执行 async 函数时,碰到了 await 的话,会立即执行紧跟 await 的语句,然后把后续代码推入微任务队列(以这种形式去理解,实际执行并非如此)。

      const syn1 = () => console.log(2)
      const syn2 = () => new Promise((r, j)=>r()).then(()=>console.log(3))
      
      async function asyncFunc () {
      console.log('start')
      await console.log(1);
      await syn1()
      await syn2()
      console.log('end')
      return 7
      }
      
      setTimeout(() => console.log(5), 0)
      console.log(0)
      asyncFunc().then(v => console.log(v))
      new Promise((r, j)=>r()).then(() => console.log(6))
      console.log(4)
      执行步骤:
      第一步:
      输入 0 执行第一个函数asyncFunc();
      第二步:
      asyncFunc() => 输入 start 等待执行完毕await console.log(1);  输入 1 
      然后跳出asyncFunc函数 就后面所以没有执行的内容全部推出优先级最高的微任务队列
      第三步:
      继续执行第一个宏任务
      输入 4 第一个宏任务同步代码执行完毕 返回到asyncFunc()继续执行
      第4步:
      执行await syn1() 等待 输出2 然后 退出asyncFunc() 函数 再把后面没有执行的任务 推入微任务 
      第5步:
      执行外面函数的微任务
      输入 6 返回到asyncFunc()继续执行
      第6步:
      执行await syn2() 等待 输出3 然后 退出asyncFunc() 函数 再把后面没有执行的任务 推入微任务 
      第7步:
      外部没有同步任务了 返回到asyncFunc()继续执行
      第8步:
      输出 end  并且返回7 
      第9步 :
      执行第二个宏任务 输出5
      
  1. 宏任务需要多次事件循环才能执行完,微任务是一次性执行完的; 一个宏任务完成以后 会把所有的微任务执行完毕

  2. 宏任务macrotask: (事件队列中的每一个事件都是一个macrotask)

优先级:主代码块 > setImmediate > MessageChannel > setTimeout / setInterval

比如:setImmediate指定的回调函数,总是排在setTimeout前面

  1. 微任务包括: 优先级:process.nextTick > Promise > MutationObserver