js事件循环:event loop

ES5中事件循环

执行以下代码:

function a() {
	b()
	console.log('a')
}
function b() {
	c()
	console.log('b')
}
function c() { 
	setTimeout(() => {
		console.log('setTimeout')
	}, 2000)
	console.log('c') 
}
a()

//输出为:c b a setTimeout

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

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

动画演示执行过程:
动画中 foo()、bar()、baz() 分别表示上文中的a()、b()、c()
在这里插入图片描述

ES6事件循环

由于ES6中新增的promise,所以任务队列变成了如下两种:

  • 宏任务队列(大家称之为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步。

执行以下代码:

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

分析代码的执行过程:

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

动画演示执行过程:
在这里插入图片描述

ES7新增async/await

执行以下代码:

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

分析代码的执行过程:

  • 首先执行console.log(‘global start’),打印出:global start。
  • 执行async1(),进入到async1函数体内,执行console.log(‘async1 start’),打印出:async1 start。接着执行await async2(),这里await关键字的作用就是await下面的代码只有当await后面的promise返回结果后才可以执行(此时,微任务队列有一事件,其实就是Promise事件),而await async2()语句就像执行普通函数一样执行async2(),进入到async2函数体中;执行console.log(‘async2’),打印出:async2。async2函数执行结束弹出执行栈。
  • 因为await关键字之后的语句已经被暂停,那么async1函数执行结束,弹出执行栈。JS主线程继续向下执行,执行foo()函数打印出:foo。
  • 执行console.log(‘global end’),打印出:global end。该语句之后再无其他需执行的代码,执行栈为空,则本轮事件执行结束。
  • 此时,事件循环机制开始工作:同理,先查看微任务队列,执行完所有已存在的微任务事件后再去查看宏任务队列。目前微任务队列中的事件即为async1函数中await async2()语句,async2函数执行完毕后,promise状态变为settled,之后的代码就可以继续执行了(可以这么理解:用一个匿名函数包裹await语句之后的代码作为一个微任务事件),执行console.log(‘async1 end’)语句,打印出:async1 end。执行栈又为空,本轮事件也执行结束。
  • 事件循环机制再查看微任务队列,发现为空,再去查看宏任务队列,发现也为空,则进入等待事件状态。

文本转载自:http://t.csdnimg.cn/bvczr

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值