对event loop的理解(举例说明javascript中event loop事件循环的运行机制)

前言

js是一种单线程的语言,所以它通过event loop机制实现了对异步任务和多线程。
首先你要对栈、队列的数据结构有一定的了解,其次还要会Promise才能看懂今天的内容。


一、在了解event loop前,我们首先要知道一些基础知识

宏任务:script全部代码、setTimeout、setInterval、setImmediate、I/O、UI Rendering。
微任务:Process.nextTick(Node 独有)、Promise 等。

event loop大体由三个部分组成:调用栈(call stack)、消息队列(Message Queue)、微任务队列(Microtask Queue)。这三个部分在event loop中非常重要。

二、event loop的运行机制

下面通过一些例子举例说明什么是event loop

1.只有宏任务时

代码如下(示例):

function fn1(){
	console.log(5)
	setTimeout(function(){
		console.log(4)
	},0)
}
function fn2(){
	console.log(6)
	fn1()
	console.log(7)
}
fn2();

首先event loop会从全局栈一行一行执行,首先遇到 fn2(),此时fn2()进入调用栈。

function fn1(){
	console.log(5)
	setTimeout(function(){
		console.log(4)
	},0)
}
function fn2(){
	console.log(6)
	fn1()
	console.log(7)
}
=>fn2();

		调用栈						消息队列
|                      |		-------------------------
|                      |								
| fn2()                |		-------------------------
------------------------

随后进入到fn2()中,遇到 console.log ,将其压入栈。

function fn1(){
	console.log(5)
	setTimeout(function(){
		console.log(4)
	},0)
}
function fn2(){
=>	console.log(6)
	fn1()
	console.log(7)
}
fn2();
	
		调用栈						消息队列
|                      |		-------------------------
| console.log(6)       |								
| fn2()                |		-------------------------
------------------------

执行console.log(6)并弹出。

function fn1(){
	console.log(5)
	setTimeout(function(){
		console.log(4)
	},0)
}
function fn2(){
=>	console.log(6)
	fn1()
	console.log(7)
}
fn2();
	
		调用栈						消息队列
|                      |		-------------------------
|                      |								
| fn2()                |		-------------------------
------------------------
//输出 6

遇到 fn1(),此时fn1()进入调用栈。

function fn1(){
	console.log(5)
	setTimeout(function(){
		console.log(4)
	},0)
}
function fn2(){
	console.log(6)
=>	fn1()
	console.log(7)
}
fn2();
	
		调用栈						消息队列
|                      |		-------------------------
| fn1()                |								
| fn2()                |		-------------------------
------------------------
//输出 6

进入fn1(),遇到console.log,将其压入栈并执行并弹出(跟上面一样),输出多了一个 5 。

function fn1(){
=>	console.log(5)
	setTimeout(function(){
		console.log(4)
	},0)
}
function fn2(){
	console.log(6)
	fn1()
	console.log(7)
}
fn2();
	
		调用栈						消息队列
|                      |		-------------------------
| fn1()                |								
| fn2()                |		-------------------------
------------------------
//输出 6 5

随后遇到setTimeout,将它的内容压入消息队列。消息队列的内容会在调用栈清空后再开始执行!

function fn1(){
	console.log(5)
=>	setTimeout(function(){
		console.log(4)
	},0)
}
function fn2(){
	console.log(6)
	fn1()
	console.log(7)
}
fn2();
	
		调用栈						消息队列
|                      |		-------------------------
| fn1()                |		console.log(4) 
| fn2()                |		-------------------------
------------------------
//输出 6 5

此时fn1执行完,从调用栈弹出,执行回到fn2,遇到console.log,将其压入调用栈。

function fn1(){
	console.log(5)
	setTimeout(function(){
		console.log(4)
	},0)
}
function fn2(){
	console.log(6)
	fn1()
=>	console.log(7)
}
fn2();
	
		调用栈						消息队列
|                      |		-------------------------
| console.log(7)       |		console.log(4) 
| fn2()                |		-------------------------
------------------------
//输出 6 5

console.log执行后,输出7,随后fn2也执行完毕从调用栈弹出,此时调用栈为空。

function fn1(){
	console.log(5)
	setTimeout(function(){
		console.log(4)
	},0)
}
function fn2(){
	console.log(6)
	fn1()
	console.log(7)
}
fn2();
	
		调用栈						消息队列
|                      |		-------------------------
|                      |		console.log(4) 
|                      |		-------------------------
------------------------
//输出 6 5 7

最后执行消息队列中的内容。

function fn1(){
	console.log(5)
	setTimeout(function(){
		console.log(4)
	},0)
}
function fn2(){
	console.log(6)
	fn1()
	console.log(7)
}
fn2();
	
		调用栈						消息队列
|                      |		-------------------------
|                      |		
|                      |		-------------------------
------------------------
//输出 6 5 7 4

总结:首先将按顺序执行,遇到函数压入栈,遇到宏任务压入消息队列,栈清空后执行消息队列。

2.即有宏任务,又有微任务

代码如下(示例):

let p = new Promise(function(resolve){  
	console.log(3)
	resolve()
})
function fn1(){
	console.log(5)
	setTimeout(function(){
		console.log(4)
	},0)
}
function fn2(){
	p.then(funtion(){
		console.log(1)
	})
	console.log(6)
	fn1()
	console.log(7)
}
fn2();

注意这一段代码与之前的有什么不同?
我们在最开始声明了一个Promise对象,并在fn2中调用了它的then方法。
此时在fn2调用前唯一的不同就是,我们在最开始创建Promise对象的时候执行了一个console.log并输出3,然后将这个Promise的状态更改为successed。

let p = new Promise(function(resolve){  
	console.log(3)
	resolve()
})
function fn1(){
	console.log(5)
	setTimeout(function(){
		console.log(4)
	},0)
}
function fn2(){
	p.then(funtion(){
		console.log(1)
	})
	console.log(6)
	fn1()
	console.log(7)
}
fn2();	
		调用栈						消息队列
|                      |		-------------------------
|                      |		
|                      |		-------------------------
------------------------
									微任务队列
								-------------------------
					
                         		-------------------------
//输出 3

接下来执行到fn2中的then方法时,会将根据promise的状态将resolve的内容压入微任务队列(此处我们不考虑构造Promise对象时异步调用resolve的情况,此处如果不理解可以查看与Promise原理有关的资料)。

let p = new Promise(function(resolve){  
	console.log(3)
	resolve()
})
function fn1(){
	console.log(5)
	setTimmout(function(){
		console.log(4)
	},0)
}
function fn2(){
	p.then(funtion(){
		console.log(1)
	})
	console.log(6)
	fn1()
	console.log(7)
}
fn2();	
		调用栈						消息队列
|                      |		-------------------------
|                      |		
| fn2()                |		-------------------------
------------------------
									微任务队列
								-------------------------
								consolo.log(1)
                         		-------------------------
//输出 3

接下来的执行都与上面一致,我们直接来到fn2执行完毕。

let p = new Promise(function(resolve){  
	console.log(3)
	resolve()
})
function fn1(){
	console.log(5)
	setTimmout(function(){
		console.log(4)
	},0)
}
function fn2(){
	p.then(funtion(){
		console.log(1)
	})
	console.log(6)
	fn1()
	console.log(7)
}
fn2();	
		调用栈						消息队列
|                      |		-------------------------
|                      |		consolo.log(4)
|                      |		-------------------------
------------------------
									微任务队列
								-------------------------
								consolo.log(1)
                         		-------------------------
//输出 3 6 5 7

此时我们发现,调用栈被清空了,此时微任务队列要优先于消息队列执行,也就是说,先执行console.log(1)再执行console.log(4)。
最终的师叔也就是3 6 5 7 1 4。

总结:遇到微任务会加入微任务队列,当调用栈清空时,如果消息队列和微任务队列都有内容,先执行微任务队列,再执行消息队列。

以上内容为个人学习过event loop后的个人理解,如有不准确的地方请及时指出。

  • 20
    点赞
  • 71
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值