宏任务与微任务

JavaScript运行机制:JavaScript是单线程的,它运行的环境般为浏览器或者Node,单线程同一个时间只能做一件事,在JavaScript运行的时候,主线程会形成一个栈(调用栈/执行栈),这个存储函数调用的栈结构遵循先进后出的原则。

任务执行模式:同步模式(Synchronous)和异步模式(Asynchronous)

JavaScript单线程的执行方式会存在一些问题,就是一些高耗时操作会带来进程阻塞;如果当一个语句也需要执行很长时间的话,比如请求数据、定时器、读取文件等等,后面的任务一直等待,为了解决这个问题JavaScript中出现了同步任务和异步任务。

同步任务

进入调用栈中在主线程上排队按照代码顺序和调用顺序执行的任务(只有前一个任务执行完毕才能执行后一个任务)形成一个执行栈,执行结束后就移出执行栈;

异步任务

进入调用栈中,然后发起调用,但其响应回调任务不进入主线程,而是进入任务队列当主线程中的任务执行完毕(即所有同步任务结束后),就从任务队列中取出任务放进主线程中来进行执行。由于主线程不断重复的获得任务、执行任务、再获取再执行,所以这种机制被叫做事件循环(Event Loop)。

异步任务不是直接进入任务队列的,也就是被调用且发起请求是前提,响应和处理数据的函数进入任务队列。

宏任务和微任务

ES6 规范中,microtask 称为 jobs,macrotask 称为 task
宏任务是由宿主发起的,而微任务由JavaScript自身发起。

根据任务执行机制,同步任务的执行栈是栈结构(先进后出),而异步任务的任务队列是队列结构(先进先出),两种模式都是有顺序的,可是异步任务难免出现非常耗时的请求,这就使得按顺序执行不合适,因此浏览器会将异步任务分为宏任务和微任务,也就是不同的任务会有不同的执行优先级。

浏览器线程

虽然JavaScript是单线程语言,但是浏览器不是单线程的,不同的线程就会对不同的事件进行处理,当对应事件可以执行的时候,对应线程就会将其放入任务队列。 浏览器线程有js引擎线程、GUI渲染线程、http异步网络请求线程、定时触发器线程、浏览器事件处理线程等。其中:

  • http异步网络请求线程:处理用户的get、post等请求,等返回结果后将回调函数推入到任务队列
  • 定时触发器线程:setIntervalsetTimeout等待时间结束后,会把执行函数推入任务队列
常见宏任务(Macrotask)
  • script
  • setTimeout
  • setInterval
  • I/O
  • requestAnimationFrame
  • UI交互事件
常见微任务(Microtask)
  • Promise.then(catch/finally)
  • async/await
    • async/await本质上还是基于Promise的一些封装,而Promise是属于微任务的一种。所以在使用await关键字与Promise.then相同;
    • sync函数在await之前的代码都是同步执行的,可以理解为await之前的代码属于new Promise时传入的代码,await之后的所有代码都是在Promise.then中的回调;
    • await之后的代码必须等await语句执行完成后(包括微任务完成),才能执行后面的,也就是说,只有运行完await语句,才把await语句后面的全部代码加入到微任务行列,所以,在遇到await promise时,必须等await promise函数执行完毕才能对await语句后面的全部代码加入到微任务中;

事件执行

总体:

主线程 -->线程上的宏任务队列1 -->宏任务队列1中创建的微任务 -->线程上的宏任务队列2–>宏任务队列2中创建的微任务…

事件循环(Event Loop)

事件循环的具体流程:

第一次执行的时候,整体代码script会放入宏任务,所以事件循环第一个宏任务开始的;微任务队列没清空之前,是不会执行下一个宏任务的。

  • 宏任务队列中,按照入队顺序,把第一个宏任务放入调用栈,开始执行;
  • 执行完该宏任务下所有同步任务后(即调用栈清空后)该宏任务被推出宏任务队列,然后微任务队列开始按照入队顺序,依次执行其中的微任务,直至微任务队列清空为止
  • 当微任务队列清空后,一个事件循环结束;
  • 接着从宏任务队列中,找到下一个执行的宏任务,开始第二个事件循环,直至宏任务队列清空为止。

示例

来更新一个有关js事件执行的输出题可以加深理解(如果是练习可以结合断点调试,可以非常清楚的看到执行的顺序)

题目
const myPromise = Promise.resolve(Promise.resolve("Promise!"));

  function func1() {

    myPromise.then(res => {

      console.log("执行第一个then", res)

      return res

    }).then(res => {

      console.log('执行第二个then', res)

    });

    setTimeout(() => console.log("Timeout!"), 0);

    console.log("Last line!");

  }

  async function func2() {

    console.log('进入func2')

    const res = await myPromise;

    console.log('执行完第一个await,继续往下执行')

    console.log(await res);

    console.log('执行完第二个await,继续往下执行')

    setTimeout(() => console.log("Timeout!"), 0);

    console.log("Last line!");

  }

  func1();

  func2();
答案:
Last line!

进入func2 

执行第一个then Promise!  

执行完第一个await,继续往下执行  

执行第二个then Promise! 

Promise!   

执行完第二个await,继续往下执行 

Last line!

Timeout!  

Timeout!
解析:
  • 首先看整体,声明了一个myPromise,两个函数func1,func2,在最下面调用了两个函数,调用顺序是先func1func2
  • 然后我们进入函数func1:在函数func1中,又分为三个块:微任务myPromise.then(异步)、宏任务setTimeout(异步)、同步任务console.log,它们都进入调用栈,同步任务进入执行栈,异步任务的响应回调进入任务队列,执行栈上的代码被执行,输出Last line!
  • 接着我们进入函数func2:在函数func2中,分为七个块,照代码顺序以此为:同步、异步、同步、同步、同步、异步、同步,注意,函数func2中出现了await,根据上文分析,函数func2的执行顺序是:await之前的同步–>await的内容–>await之后的内容,所以执行打印–>执行第一个await包括微任务完成–>执行打印–>执行第二await包括微任务完成–>执行打印–>宏任务进入任务队列–>执行打印
  • 好的,到此我们已经结束了第一轮,该把任务队列拿出来遛遛了,任务队列中目前排队的有func1setTimeoutfunc2setTimeout,让它们按照队列先进先出的原则依次执行;
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值