详细讲解事件循环、宏任务与微任务(附三个详细例题讲解!!)

知识铺垫

单线程模型:由于JavaScript被设计为用在浏览器环境,而该环境下存在大量可能发生冲突的DOM操作,为了避免进行复杂的冲突处理(可能存在的冲突数量几乎不可预测),JavaScript的设计者舍弃了java的多线程模型(该模型下,执行引擎同时可以做几件事,但要进行线程同步),将其设计成了一门单线程语言(执行引擎在同一时间只做一件事)。

注意:这里的单线程是指JavaScript的主线程只有一个。除了这个主线程,JavaScript还有一个I/O线程,通过事件循环来处理I/O问题,但两者之间相对独立,不需要进行状态同步,因此我们仍然可以把JavaScript看成一门单线程语言。

任务队列:所谓任务队列,就是用于存储等待执行的任务的队列。由于JavaScript是一门单线程语言,如果当前有一个任务需要执行,但JavaScript引擎正在执行其他任务,那么这个任务就需要放进一个队列中进行等待。等到线程空闲时,就可以从这个队列中取出最早加入的任务进行执行

(单线程相当于说这家银行只有一个服务窗口,一次只能为一个人服务,后面到的就需要排队,而任务队列就是排队区,先到的就优先服务)。

注意:如果当前线程空闲,并且队列为空,那每次加入队列的函数将立即执行。

事件循环机制(Event Loop)

含义:事件循环分为两种,分别是浏览器事件循环node.js事件循环

JS的主要运行环境有两个,浏览器和Node.js

事件循环又分为同步任务和异步任务

同步任务

含义:在主线程上排队执行的任务,只有一个任务执行完毕,才能执行后一个任务

异步任务

含义:不进入主线程,而进入“任务队列(task queue)”的任务,只有“任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

分类:异步任务又分为宏任务和微任务。

所有同步任务都在主线程上执行,形成一个函数调用栈(执行栈),而异步则先放到任务队列(task queue)里,任务队列又分为宏任务(macro-task)微任务(micro-task)。

宏任务

宏任务包括:script(整体代码)、setTimout、setInterval、setImmediate(node.js环境)、I/O、UI交互事件

微任务

微任务包括:promise.then(new promise构造函数是同步)、async/await。

若同时存在promise和nextTick,则先执行nextTick

执行过程

1.所有同步任务都在主线程上执行,形成一个执行栈(调用栈)

2.主线程之外,还存在一个‘任务队列’(task queue),浏览器中的各种 Web API 为异步的代码提供了一个单独的运行空间,当异步的代码运行完毕以后,会将代码中的回调送入到 任务队列中(队列遵循先进先出得原则)

3.一旦主线程的栈中的所有同步任务执行完毕后,调用栈为空时系统就会将队列中的回调函数依次压入调用栈中执行,当调用栈为空时,仍然会不断循环检测任务队列中是否有代码需要执行

执行顺序

先执行同步代码,

遇到异步宏任务则将异步宏任务放入宏任务队列中,

遇到异步微任务则将异步微任务放入微任务队列中,

所有同步代码执行完毕后,再将异步微任务从队列中调入主线程执行,

微任务执行完毕后再将异步宏任务从队列中调入主线程执行,

一直循环直至所有任务执行完毕。

注意:当宏任务和微任务都处于 任务队列(Task Queue) 中时,微任务的优先级大于宏任务,即先将微任务执行完,再执行宏任务;

下面是详细的示例讲解

难度逐个增加

示例1
//遇到setTimout,异步宏任务,放入宏任务队列中;
setTimeout(function(){
  	//从宏任务队列中取出任务到主线程中,输出1,宏任务队列为空,结束~
    console.log('1');
});
//遇到new Promise,Promise在实例化的过程中所执行的代码都是同步进行的,所以输出2;
new Promise(function(resolve){          
    console.log('2');
    resolve();
// 而Promise.then中注册的回调才是异步执行的,将其放入微任务队列中
}).then(function(){    
  	//从微任务队列中取出任务到主线程中,输出3,微任务队列为空
    console.log('3');
});        
//遇到同步任务console.log(‘4’);输出4;主线程中同步任务执行完
console.log('4'); 
//输出顺序:2 4 3 1

遇到setTimout,异步宏任务,放入宏任务队列中;

遇到new Promise,Promise在实例化的过程中所执行的代码都是同步进行的,所以输出2;

而Promise.then中注册的回调才是异步执行的,将其放入微任务队列中

遇到同步任务console.log(‘4’);输出4;主线程中同步任务执行完

从微任务队列中取出任务到主线程中,输出3,微任务队列为空

从宏任务队列中取出任务到主线程中,输出1,宏任务队列为空,结束~

注意宏任务内部嵌套了一个微任务的情况,或者微任务内部嵌套微任务 ,只有外层的异步任务在主线程执行完毕后,才能执行判断内部的微任务/宏任务,并放入任务队列中。

示例2
//遇到setTimeout,异步宏任务,将() => {console.log(4)}放入宏任务队列中
setTimeout(()=>{
  new Promise(resolve =>{
    resolve();
  }).then(()=>{
  //此时由于回调还未执行,所以无法将setTimeout内部的 console.log('test')放入微任务队列中
  //异步微任务d
    console.log('test');
  });
	//异步宏任务
  console.log(4);
});

new Promise(resolve => {
  resolve();
  //同步任务 输出1
  console.log(1)
}).then( () => {
  //异步微任务,放入队列中
  console.log(3);
  //由于是在.then回调之后才进行进行,因此等待微任务队列继续执行
  Promise.resolve().then(() => {
    //异步微任务a
    console.log('before timeout');
    //此时又有一个.then,这个是异步微任务b
  }).then(() => {
    Promise.resolve().then(() => {
      //异步微任务c
      console.log('also before timeout')
    })
  })
})
//同步任务 输出2
console.log(2); 
//输出:1 2 3 before timeout also before timeout 4 test

遇到setTimeout,异步宏任务,将() => {console.log(4)}放入宏任务队列中,此时由于回调还未执行,所以无法将setTimeout内部的 console.log('test');放入微任务队列

遇到new Promise,Promise在实例化的过程中所执行的代码都是同步进行的,所以输出1;

而Promise.then中注册的回调才是异步执行的,将其放入微任务队列中,同理,内部的 console.log('before timeout')与 console.log('also before timeout')都尚未放入微任务队列

遇到同步任务console.log(2),输出2;主线程中同步任务执行完

微任务队列中取出任务到主线程中,输出3,此微任务中又有微任务Promise.resolve().then(微任务a).then(微任务b),将其依次放入微任务队列中;

微任务队列中取出任务a到主线程中,输出 before timeout;

微任务队列中取出任务b到主线程中,任务b又注册了一个微任务c,放入微任务队列中;

微任务队列中取出任务c到主线程中,输出 also before timeout微任务队列为空

宏任务队列中取出任务到主线程,此任务中注册了一个微任务d,将其放入微任务队列中,接下来遇到输出4,宏任务队列为空

从微任务队列中取出任务d到主线程 ,输出test,微任务队列为空,结束

示例3
async function async1() {
  //同步任务
    console.log('async1 start');
    await async2();
  //执行完async2()后才执行这里
    console.log('async1 end');
}
async function async2() {
    //async2做出如下更改:
    new Promise(function(resolve) {
    //同步任务
    console.log('promise1');
    resolve();
}).then(function() {
    //异步微任务
    console.log('promise2');
    });
}
//同步任务
console.log('script start');

setTimeout(function() {
  //异步宏任务
    console.log('setTimeout');
}, 0)
//执行async1()
async1();

new Promise(function(resolve) {
  //同步任务
    console.log('promise3');
    resolve();
}).then(function() {
  //异步微任务
    console.log('promise4');
});
//同步任务
console.log('script end');

// script start
// async1 start
// promise1
// promise3
// script end
// promise2
// async1 end
// promise4
// setTimeout

script start

同步任务直接执行,此时遇到settimeout推入异步宏任务,继续遇到async1()

async1 start

async1同步任务直接执行,await是异步,下面的console.log推入异步微任务,执行async2()

promise1

promise的声明是同步任务,直接执行,“.then”后面的内容是异步,推入异步微任务,继续向下寻找是否还有同步任务

promise3

promise的声明是同步任务,直接执行,“.then”后面的内容是异步,推入异步微任务,继续向下寻找是否还有同步任务

script end

所有的同步任务执行完毕,按“先进先出”顺序从微任务队列中执行

promise2

async1 end

promise4

setTimeout

可能有些人对await的这个执行逻辑有点懵,这样转换一下可能就好理解一些,关于异步制的讲解笔者也有讲过       文章链接:  http://t.csdn.cn/rCYVT

async function async1() {
	console.log('async1 start');
	await async2();
	console.log('async1 end');
}

等价于

async function async1() {
	console.log('async1 start');
	Promise.resolve(async2()).then(() => {
                console.log('async1 end');
        })
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值