聊聊--宏任务和微任务、同步和异步

JavaScript018

聊聊--宏任务和微任务、同步和异步,第1张

最近几天,面试了几个应聘的。有一道面试题,答案真是千奇百怪。就是这么一道题:

setTimeout(() =>{

console.log("setTimeout")

})

let p1 =new Promise((resolve) =>{

console.log("Promise1")

    resolve("Promise2")

})

p1.then((res) =>{

console.log(res)

})

console.log("over")

当然有的答对的可能是见过这么一道相似的类似题目,我们来简单聊一聊这是一道什么样的题。

接触JS的肯定都知道,JS是单线程的。而单线程就意味着所有的任务都要进行排队,依次执行,这就是任务队列。但是会有那样的任务执行起来很耗时间,而我们还有很多没有利用的CPU空间。所以就产生了:JS中任务是有同步(synchronous)和异步(asynchronous)的分别的。同步就是顺次执行的那一种,而异步就是需要等待的那一种。同步和异步的执行路线是不同的:同步会进入主线程顺次执行。只有前一个执行结束才会执行下一个;而异步会进入一个等待的队列,可以理解为等待中的任务队列(任务队列就是一个先进先出的队列结构,主线程的执行队列的读取是自动的)。在这里异步任务会在Event Table中注册相应的回调函数。当指定的步骤完成了,Event Table会将注册了的回调函数推送到Event Queue,在Event Queue这里进行等待上场。当主线程内的任务执行完毕为空之后,就会到Event Queue读取相应的函数,进入主线程执行。这个过程不断重复构成Event Loop。

这个就是同步与异步的简单过程。

但是,会有一个疑问,向上面的那道题目中的SetTimeout和promise都是异步任务那他俩的执行属性呢?难道是顺次,这个还真不一定。

JS的异步也是有一个机制的,就是会分为宏任务和微任务。宏任务和微任务会放到不同的event queue中,先将所有的宏任务放到一个event queue,再将微任务放到一个event queue中。执行完宏任务之后,就会先从微任务中取这个回调函数执行。

那么什么是宏任务呢,一般就是setTimeout、setInterval、script、I/O

微任务就是,Promise\process.nextTick

让我们回到那道题,首先会执行script下的宏任务,解析,遇到setTimeout,判断它是一个宏任务类型,将其放到一个宏任务的queue中;遇到Promise,开始执行,打印 Promise1 ;往下遇到then,判断是个微任务,放到微任务的队列中,等待执行;继续解析,打印 over 。查证本轮次的宏任务都执行完毕了,开始执行本轮的微任务,发现有一个then的回调函数,打印 Promise2 , 到这里微任务页执行完毕了,这样本轮次的Loop全部执行完毕,开始下一轮次的Loop。发现宏任务的队列有一个setTimeout的回调函数,打印 setTimeout 。

一、同步和异步

所谓单线程,无非就是同步队列和异步队列,js代码是自上向下执行的,在主线程中立即执行的就是同步任务,比如简单的逻辑操作及函数,而异步任务不会立马立马执行,会挪步放到到异步队列中,比如ajax、promise、事件、计时器等等。

也就是先执行同步,主线程结束后再按照异步的顺序再次执行。

二、时间循环(Event Loop)

Event Loop是什么?中文翻译是事件循环,等待主线程中任务全部完成后,再回来把异步队列中任务放到主程序中运行,这样反复的循环,就是事件循环。

b14d903a712b31c34f347ba7d64b697e.png

先来看组代码

console.log('开始111') setTimeout(function () {console.log('setTimeout111') }, 0) Promise.resolve().then(function () {console.log('promise111') }).then(function () {console.log('promise222') }) console.log('开始222')

打印 “开始111”,再打印“开始222”。

中途的三个异步,进入到了异步队列,等待同步执行完(打印完),返回来再执行异步,所以是后打印出来。

打印的结果先放一放,我们稍后回来再说。现在我们中途插播一段知识点:

三、宏观任务和微观任务(先执行微观任务,再执行宏观任务):

在事件循环中,每进行一次循环操作称为tick,tick 的任务处理模型是比较复杂的,里边有两个词:分别是 Macro Task (宏任务)和 Micro Task(微任务)。

简单来说:

宏观任务主要包含:setTimeout、setInterval、script(整体代码)、I/O、UI 交互事件、setImmediate(Node.js 环境)

微观任务主要包括:Promise、MutaionObserver、process.nextTick(Node.js 环境)

规范:先执行微观任务,再执行宏观任务

那么我们知道了,Promise 属于微观任务, setTimeout、setInterval 属于宏观任务,先执行微观任务,等微观任务执行完,再执行宏观任务。所以我们再看一下这个代码:

console.log('开始111') setTimeout(function () {console.log('setTimeout111') }, 0) Promise.resolve().then(function () {console.log('promise111') }).then(function () {console.log('promise222') }) console.log('开始222')

我们按照步骤来分析下:

1、遇到同步任务,直接先打印 “开始111”。

2、遇到异步 setTimeout ,先放到队列中等待执行。

3、遇到了 Promise ,放到等待队列中。

4、遇到同步任务,直接打印 “开始222”。

5、同步执行完,返回执行队列中的代码,从上往下执行,发现有宏观任务 setTimeout 和微观任务 Promise ,那么先执行微观任务,再执行宏观任务。

所以打印的顺序为:开始111 、开始222 、 promise111 、 promise222 、 setTimeout111 。

同理,我们再来分析一个代码:

console.log('开始111')setTimeout(function () { console.log('timeout111')})new Promise(resolve =>{ console.log('promise111') resolve() setTimeout(() =>console.log('timeout222'))}).then(function () { console.log('promise222')})console.log('开始222')

分析一下:

1、遇到同步代码,先打印 “开始111” 。

2、遇到setTimeout异步,放入队列,等待执行 。

3、中途遇到Promise函数,函数直接执行,打印 “promise111”。

4、遇到setTimeout ,属于异步,放入队列,等待执行。

5、遇到Promise的then等待成功返回,异步,放入队列。

6、遇到同步,打印 “开始222”。

7、执行完,返回,将异步队列中的代码,按顺序执行。有一个微观任务,then后的,所以打印 “promise222”,再执行两个宏观任务 “timeout111” “timeout222”。

所以,打印的顺序为:开始111 、 promise111 、 开始222 、 promise222 、 timeout111 、 timeout222 .

先执行主任务,把异步任务放入循环队列当中,等待主任务执行完,再执行队列中的异步任务。异步任务先执行微观任务,再执行宏观任务。一直这样循环,反复执行,就是事件循环机制。