js事件循环机制(event loop)详解,有这一篇就够了

一、首先来执行以下代码:代码1

function foo() {
	bar()
	console.log('foo')
}
function bar() {
	baz()
	console.log('bar')
}
function baz() { 
	setTimeout(() => {
		console.log('setTimeout')
	}, 2000)
	console.log('baz') 
}

foo()

输出为

baz bar foo setTimeout

那他的执行顺序是怎样的呢?
先看一下ES5的事件循环
在这里插入图片描述
对照上图我们分析一下代码1的执行过程:

  • 遇到foo函数被调用,将foo函数压入执行栈。
  • 执行foo函数,发现foo函数体中调用了bar函数,则将bar函数压入执行栈。
  • 执行bar函数,发现bar函数体中调用了baz函数,又将baz函数压入执行栈。
  • 执行baz函数,函数体中第一句为setTimeout函数,js线程将其交给Web APIs(Web APIs会将其按照一定的规则加入任务队列),自己继续往下执行console.log('baz'),在控制台打印:baz,然后baz函数执行完毕弹出执行栈。
  • 此时的栈顶为bar函数,bar函数体中的baz()语句已经执行完,接着执行console.log('bar'),在控制台打印:bar,然后bar函数执行完毕弹出执行栈。
  • 此时的栈顶为foo函数,foo函数体中的bar()语句已经执行完,接着执行console.log('foo'),在控制台打印:foo,然后foo函数执行完毕弹出执行栈。
  • 至此,执行栈为空,这一轮执行完毕。
  • 接下来就回去任务队列中查看是否有待执行的任务,发现有,则执行console.log('setTimeout')

过程如下:
在这里插入图片描述
二、ES6中新增的promise
ES6中新增的promise,ES6+标准中的任务队列也新增了一种,变成了如下两种:

  • 宏任务队列(大家称之为macrotask queue,即callback queue):按HTML标准严格来说,其实没有macrotask queue这种说法,它也就是ES5中的事件队列,该队列存放的是:DOM事件、AJAX事件、setTimeout事件等的回调。可以通过setTimeout(func)即可将func函数添加到宏任务队列中。
  • 微任务队列(microtask queue):存放的是Promise事件、nextTick事件(Node.js)等。有一个特殊的函数queueMicrotask(func)可以将func函数添加到微任务队列中。

那么,现在的事件循环模型就变成了如下的样子:
在这里插入图片描述

  1. JS线程负责处理JS代码,当遇到一些异步操作的时候,则将这些异步事件移交给Web APIs 处理,自己则继续往下执行。
  2. Web APIs线程将接收到的事件按照一定规则添加到任务队列中,宏事件(DOM事件、Ajax事件、setTimeout事件等)添加到宏任务队列中,微事件(Promise、nextTick)添加到微事件队列中。
  3. JS线程处理完当前的所有任务以后(执行栈为空),它会先去微任务队列获取事件,并将微任务队列中的所有事件一件件执行完毕,直到微任务队列为空后再去宏任务队列中取出一个事件执行(每次取完一个宏任务队列中的事件执行完毕后,都先检查微任务队列)。
  4. 然后不断循环第3步。

接下来执行如下代码:代码2

function foo() {
	console.log('foo')
}

console.log('global start')

setTimeout(() => {
	console.log('setTimeout:')
}, 0)

new Promise((resolve) => {
	console.log('promise')
	resolve()
}).then(() => {
	console.log('promise then')
})

foo()

console.log('global end')

输出为:

global start
promise
foo
global end
promise then
setTimeout

代码执行的解释:

  1. 执行console.log('global start')语句,打印出:global start。
  2. 继续往下执行,遇到setTimeout,JS执行栈将其移交给Web API处理。 延迟0秒后,Web API将setTimeout事件添加到宏任务队列。
  3. JS线程转交setTimeout事件后自己则继续往下执行,遇到new Promise(…),执行之,Promise参数中的匿名函数同步执行,执行console.log('promise')打印出:promise。在执行resolve()之后Promise状态变为resolved,再继续执行then(…),遇到then则将其提交给Web API处理,Web API将其添加到微任务队列。
  4. 执行栈在转交完Promise事件后,继续往下执行,到达语句foo(),执行foo函数,打印出foo。
  5. 执行栈继续执行,到达语句console.log('global end'),执行后打印出:global end。至此,本轮事件循环已结束,执行栈为空。
  6. 事件循环机制首先查看微任务队列是否为空,发现有一个Promise事件待执行,则将其压入执行栈,执行then中的代码,执行console.log('promise then'),打印出:promise then。至此,新的一轮事件循环(Promise事件)已结束,执行栈为空。
  7. 执行栈变空后又先查看微任务队列,发现微任务队列已为空,然后再查看宏任务队列,发现有setTimeout事件待处理,则将setTimeout中的匿名函数压入执行栈中执行,执行console.log('setTimeout')语句,打印出:setTimeout: 0s。至此,新的一轮事件循环(setTimeout事件)已结束,执行栈为空。
  8. 执行栈变空后又先查看微任务队列,发现微任务队列已为空,然后再查看宏任务队列,发现宏任务队列也为空,那么执行栈进入等待事件状态。

再来看下执行过程:
在这里插入图片描述三、事件循环中的async/await
(不了解的人建议先去补习一下:async

function foo() {
	console.log('foo')
}

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

async function async2() {
	console.log('async2')
}

console.log('global start')
async1()
foo()
console.log('global end')

输出结果为:

global start
async1 start
async2
foo
global end
async1 end

我们再来逐条解析一下代码的执行过程吧:

  1. 首先执行console.log(‘global start’),打印出:global start。

  2. 执行async1(),进入到async1函数体内,执行console.log(‘async1 start’),打印出:async1 start。接着执行await async2(),这里await关键字的作用就是await下面的代码只有当await后面的promise返回结果后才可以执行(此时,微任务队列有一事件,其实就是Promise事件),而await async2()语句就像执行普通函数一样执行async2(),进入到async2函数体中;执行console.log(‘async2’),打印出:async2。async2函数执行结束弹出执行栈。

  3. 因为await关键字之后的语句已经被暂停,那么async1函数执行结束,弹出执行栈。JS主线程继续向下执行,执行foo()函数打印出:foo。

  4. 执行console.log(‘global end’),打印出:global end。该语句之后再无其他需执行的代码,执行栈为空,则本轮事件执行结束。

  5. 此时,事件循环机制开始工作:同理,先查看微任务队列,执行完所有已存在的微任务事件后再去查看宏任务队列。目前微任务队列中的事件即为async1函数中await async2()语句,async2函数执行完毕后,promise状态变为settled,之后的代码就可以继续执行了(可以这么理解:用一个匿名函数包裹await语句之后的代码作为一个微任务事件),执行console.log(‘async1 end’)语句,打印出:async1 end。执行栈又为空,本轮事件也执行结束。

  6. 事件循环机制再查看微任务队列,发现为空,再去查看宏任务队列,发现也为空,则进入等待事件状态。

文本转载自:https://blog.csdn.net/cc18868876837/article/details/97107219
原博主写的超详细,我在这里简单写了一下,方便以后自己复习,如果上面的看完还不太理解,建议可以去看下原文。

  • 7
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值