事件循环(Event loop)
事件循环即Event loop,是JavaScript的一种执行机制,为的是解决单线程的js在执行代码时不发生阻塞的一种机制。
在理解Event loop之前,需要先引入两个概念,即栈和队列。
两个不恰当的比喻,把栈比作一个瓶子,而队列则是一根的管子。
用这两种容器来装水,假设我们把水倒到容器里时,水并不会发生流动。那么杯子里的水要倒出来,则会发生先加入的水最后才被倒出来(先进后出),且不论是加水还是倒水都只能从瓶口操作。
反观管子,我们将水从一侧加入,水便会从另外一侧流出(先进先出),且我们只能从侧加水让水另一侧流出,没法让其从一侧加从一侧流出(因为我们的假设是水不会发生流动)。
对上述概念理解了之后,我们在来看栈和队列的定义,相信结合着这个两个例子,定义理解起来也会容易很多。
栈(stack)
栈在计算机科学中是限定仅在表尾进行插入或删除操作的线性表。栈是一种数据结构,它按照先进后出的原则存储数据,先进入的数据被压入栈底,后进入的数据在栈顶,需要读数据的时候从栈顶开始弹出数据。
栈是只能在某一端插入和删除的特殊线性表。如下图所示。
队列(queue)
和栈一样,队列是一种操作受限制的线性表。
进行插入操作的端称为队尾,进行删除操作的端称为队头。 队列中没有元素时,称为空队列。
队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出。如下图所示。
同步任务和异步任务
js中将任务分为同步任务和异步任务,其中同步任务在执行栈中依照顺序依次执行。
而异步任务则交由浏览器去执行,当异步任务执行完以后,会被放入任务队列中,等执行栈中的任务执行完了以后,会将任务队列中的任务放到执行栈中执行。
执行栈中任务执行完,又会去轮询任务队列,这个过程不停循环进行,因此,也叫事件轮询或事件循环。
js的执行过程为:执行栈=>任务队列,任务队列中的任务都为异步任务执行后的回调,而异步任务又分为宏任务和微任务,两者的执行顺序是先执行微任务,然后再执行宏任务,即微任务=>宏任务。(也有一种说法是先执行宏任务再执行微任务,因为script代码也属于宏任务)如已了解宏任务与微任务可跳过以下内容至例子。如图3为事件循环的图示。
宏任务与微任务
我们不难发现宏任务和微任务是同属于一个队列的,因此在执行时本轮循环中的微任务实际上是在插队,这样微任务中所做的状态修改,在下一轮事件循环中也能得到同步。
因此执行过程为:先执行微任务,执行完后如果没有微任务,就执行下一个宏任务,如果有微任务,就按顺序一个一个执行微任务
宏任务与微任务的种类
宏任务:script、setTimeout、setInterval、postMessage、MessageChannel、UI render(浏览器独有)、setImmediate(Node.js 环境独有)
微任务:Promise.then、Object.observe、MutationObserver、process.nextTick(Node.js 环境)
目前我常遇到的就只有宏任务:script、setTimeout、setInterval和微任务:Promise.then,另外async和await一起时,await会将它之后的代码变为微任务。
例子
console.log('1、script1');
setTimeout(function() {
console.log('5、宏任务');
}, 0);
Promise.resolve().then(function() {
console.log('3、微任务1');
}).then(function() {
console.log('4、微任务2');
});
console.log('2、script2');
///执行结果应为:
//1、script1
//2、script2
//3、微任务1
//4、微任务2
//5、宏任务
下面为执行过程的图解
首先整个script被压入执行栈中,然后开始执行同步代码,即先执行console.log(‘1、script1’)再执行console.log(‘2、script2’)。
然后我们可以看到setTimeout为宏任务,promise.then为微任务,此处为两个.then,所以有两个微任务。
在执行时,先执行微任务,后执行宏任务。两个微任务被推入任务队列中,任务队列遵循先进先出原则,所以执行顺序为:console.log(‘3、微任务1’)=>console.log(‘4、微任务2’),当微任务执行完以后,将宏任务推入任务队列中,随后将任务队列中的宏任务压入执行栈中执行console.log(‘5、宏任务’)。最后执行完宏任务,script也出栈,程序整体就执行完了。打印结果为:
1、script1
2、script2
3、微任务1
4、微任务2
5、宏任务
练习
///
console.log('1、Script')
setTimeout(() => {
console.log('4、宏任务1')
Promise.resolve().then(function() {
console.log('5、微任务2')
})
}, 0)
setTimeout(() => {
console.log('6、宏任务2')
Promise.resolve().then(function() {
console.log('7、微任务3')
})
}, 0)
Promise.resolve().then(function() {
console.log('3、微任务1')
})
console.log('2、Script')
//1、Script
//2、Script
//3、微任务1
//4、宏任务1
//5、微任务2
//6、宏任务2
//7、微任务3
//最后这个练习打印的结果为1 2 3 4 5 6 7 8 9 10。
//其中有一个小坑就是promise本身是同步的,只有.then才是异步的微任务。
console.log(1)
setTimeout(function() {
console.log(5)
new Promise(function(resolve) {
console.log(6)
resolve()
}).then(function() {
console.log(7)
})
})
new Promise(function(resolve) {
console.log(2)
resolve()
}).then(function() {
console.log(4)
})
setTimeout(function() {
console.log(8)
new Promise(function(resolve) {
console.log(9)
resolve()
}).then(function() {
console.log(10)
})
})
console.log(3)
以上为我个人在学习过程中的总结,欢迎大家一起学习讨论,如有不对,我非常乐意你能指出来。
最后非常感谢你的阅读。