对event loop的理解(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后的个人理解,如有不准确的地方请及时指出。

猜你喜欢

转载自blog.csdn.net/qq_15267283/article/details/113650572