一、Event Loop
Event Loop
即事件循环,是指 浏览器
或Node
(宿主) 的一种解决javaScript
单线程运行时不会阻塞的一种机制,也就是我们经常使用异步的原理。
二、同步任务和异步任务
Javascript
单线程任务被分为同步任务和异步任务,同步任务会在调用栈中按照顺序等待主线程依次执行,异步任务会在异步任务有了结果后,将注册的回调函数放入任务队列中等待主线程空闲的时候(调用栈被清空),被读取到栈内等待主线程的执行。
主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。 上述过程会不断重复,也就是常说的Event Loop
(事件循环)
注意:队列(Queue)遵循先进先出原则
三、宏任务:(macrotask
)
宏任务是由宿主( 浏览器window
或Node
)发起的。
前端开发常见宏任务:包括整体代码script
、setTimeout
、setInterval
、setImmediate
。
四、微任务:(microtask
)
微任务由JavaScript
自身发起。
前端开发常见微任务:Promise
、Process.nextTick(Node独有)
下面用一个经典题目做具体分析:
console.log('1');
// 定义注解 setTimeout_1 用于下文使用方便
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
// setTimeout_2
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
// 输出结果: 1 7 6 8 2 4 3 5 9 11 10 12
1、第一轮事件循环流程分析如下:
(1)整体script
作为第一个宏任务进入主线程,同步 console.log('1')
执行,输出1
(2)执行 setTimeout_1
异步 ,将其回调函数分发到宏任务Event Queue
(队列)中
(3)执行 process.nextTick
异步 ,将其回调函数分发到微任务Event Queue
中
(4)执行 new Promise
,Promise 新建后立即执行,所以 console.log('7')
执行,输出 7
,.then
是异步,将其分发到微任务Event Queue
中
(5)执行 setTimeout_2
异步 ,将其分发到宏任务Event Queue
(队列)中
第一轮事件循环宏任务结束时各Event Queue的情况:
同步代码输出结果为: 1、7
宏任务Event Queue
: setTimeout_1
、setTimeout_2
微任务Event Queue
:整体script
中的 process.nextTick
、new Promise().then()
主线程内的任务执行完毕为空,会去微任务Event Queue读取对应的函数,进入主线程执行:
此时执行process.nextTick
、new Promise().then()
输出结果为:6、8
第一轮完毕输出结果: 1、7、6、8
重点:微任务Event Queue执行完毕为空,会去宏任务Event Queue读取对应的函数,到此第一轮事件循环彻底结束,第二轮事件循环开始
2、第二轮事件循环流程分析如下: (执行宏任务Event Queue
中的setTimeout_1
)
(1)setTimeout_1
进入主线程,同步 console.log('2')
执行,输出2
(2)执行 process.nextTick
异步 ,将其回调函数分发到微任务Event Queue
中
(3)执行 new Promise
,Promise 新建后立即执行,所以 console.log('4')
执行,输出 4
,.then
是异步,将其分发到微任务Event Queue
中
第二轮事件循环宏任务结束时各Event Queue的情况:
同步代码输出结果为: 2、4
宏任务Event Queue
: setTimeout_2
微任务Event Queue
:setTimeout_1
中的 process.nextTick
、new Promise().then()
第二轮主线程内的任务执行完毕为空,会去微任务Event Queue读取对应的函数
第二轮完毕输出结果: 2、4、3、5
第二轮事件循环彻底结束,第三轮setTimeout_2
事件循环开始
3、第三轮事件循环流程分析如下: (执行宏任务Event Queue
中的setTimeout_2
)
(1)setTimeout_2
进入主线程,同步 console.log('9')
执行,输出9
(2)执行 process.nextTick
异步 ,将其回调函数分发到微任务Event Queue
中
(3)执行 new Promise
,Promise 新建后立即执行,所以 console.log('11')
执行,输出 11
,.then
是异步,将其分发到微任务Event Queue
中
第三轮主线程内的任务执行完毕为空,会去微任务Event Queue读取对应的函数
第三轮完毕输出结果: 9、11、10、12
第三轮执行完毕,至此任务队列皆为空,整段代码,共进行了三次事件循环
完整的输出为1,7,6,8,2,4,3,5,9,11,10,12。
总结:整体script
作为第一个宏任务进入主线程,进行第一次事件循环,若遇到宏任务,则将其回调函数分发到宏任务队列中、若遇到微任务,则将其回调函数分发到微任务队列中。同步代码执行完毕,微任务队列进入主线程直至任务执行完毕为空,下一个宏任务进入主线程意味着开始进行第二次事件循环。上述过程会不断重复,直到宏任务队列执行完毕为空,也就是常说的Event Loop
(事件循环)机制了。