什么是Event Loop?
Event Loop 是一个很重要的概念,指的是计算机系统的一种运行机制,决定了同步代码和异步代码的执行方式。
JavaScript语言就采用这种机制,来解决单线程运行带来的一些问题。
Event Loop在不同的地方有不同的实现,浏览器和Node.JS基于不同的技术实现了各自的Event Loop。
- 浏览器的Event Loop是在HTML5的规范中明确定义。
- Node.JS的Event Loop是基于libuv实现的。
- libuv已经对Event Loop作出了实现,而HTML5规范中只是定义了浏览器中的Event Loop模型,具体的实现留给了浏览器厂商。
执行栈(调用栈)
Javascript 有一个 main thread 主线程和 call-stack 调用栈(执行栈),所有的任务都会被放到调用栈等待主线程执行。
JS调用栈采用的是后进先出的规则,当函数执行的时候,会被添加到栈的顶部,当执行栈执行完成后,就会从栈顶移出,直到栈内被清空。
宏任务
- setTimeout
- setInterval
- setImmediate(Node独有)
- requestAnimationFrame(浏览器独有)
- I/O
- UI rendering(浏览器独有)
微任务
- process.nextTick(Node独有)
- Promise
- Object.observe
- MutationObserver
代码执行顺序:
- 执行全局JS同步代码,这些同步代码中有一些是同步语句,有一些是异步语句
- 全局Script代码执行完毕后,执行栈清空
- 从微任务队列中取出位于队首的任务,放入到执行栈中执行,该任务执行完毕后,继续从微任务队列中取出下一个任务,直到微任务队列清空
- 从宏任务队列中取出位于队首的任务,放入到执行栈中执行,该任务执行完毕后,继续从宏任务队列中取出下一个任务,直到宏任务队列清空
注意: 如果在执行第3步时,遇到宏任务,则将任务追加到宏任务队列的队尾,继续执行微任务队列里面的任务;如果在执行第4步时,遇到微任务,则将任务放到微任务队列,微任务队列将该任务放到执行栈中执行,直到微任务队列清空,再继续返回来执行宏任务 - 全部执行完毕后,执行栈为空
例子1:
// 1、循环输出0~9,此时i=10
let i;
for (i = 0; i < 10; i++){
console.log(i);
}
// 2、开启延时器,先将其放到window上,过1000毫秒后将其放入宏任务队列
// 5、执行栈清空后,由于微任务队列中没有任务,所以直接来执行宏任务队列里面的任务,将位于队首的任务放到执行栈中执行,延时1000毫秒后,输出20
setTimeout(function(){
console.log(i);
}, 1000)
// 3、开启延时器,先将其放到window上,过2000毫秒后将其放入宏队列
// 6、上一个任务执行结束后,将该任务放到执行栈中执行,延时2000毫秒后,输出hello
setTimeout(function(){
console.log("hello");
}, 2000)
// 4、循环开始时i=10,循环输出10~19,此时i=20,执行栈清空
for (; i < 20; i++){
console.log(i);
}
最后输出的顺序为:0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 hello
例子2:
// 1、定义一个变量并赋值为1
let a = 1;
// 2、开启定时器,并将其放到windon上,过1000毫秒后将其放入宏任务队列
setTimeout(function(){
// 11、微任务队列清空后,执行宏任务里面的任务,将任务放到执行栈中执行,延时1000毫秒后,输出1
console.log(a);
}, 1000);
// 3、输出2
console.log(2);
// 4、初始化一个Promise实例
let p = new Promise(function(resolve, reject){
// 5、输出3
console.log(3);
// 6、状态变为成功
resolve();
});
// 7、输出4
console.log(4);
// 8、绑定then,因为状态已经发生变化,所以立即将这个函数放入微任务队列
p.then(function(){
// 10、执行栈清空后,执行微任务队列里面的任务,将任务放到执行栈中执行,输出5,此时微任务队列清空
console.log(5);
});
// 9、输出6,此时执行栈清空
console.log(6);
最后输出顺序为:2 3 4 6 5 1
例子3:
// 1、输出1
console.log(1);
// 2、开启定时器,将其放到window上,过0毫秒后将其放入宏任务队列
setTimeout(function() {
// 14、输出2
console.log(2)
}, 0)
// 3、初始化一个Promise实例
new Promise(function(resolve) {
// 4、输出3
console.log(3)
// 5、状态变为成功
resolve();
// 6、绑定then,因为状态已经发生变化,所以立即将这个函数放入到微任务队列
}).then(function() {
// 11、输出4
console.log(4)
// 12、开启定时器,将其放到window上,过0毫秒后将其放入宏任务队列
setTimeout(function() {
// 15、输出5
console.log(5)
}, 0)
})
// 7、输出6
console.log(6);
// 8、初始化一个Promise实例
new Promise(function(resolve, reject) {
// 9、状态变为失败
reject()
})
.then(function() {
})
// 10、绑定catch,因为状态已经发生变化,所以立即将这个函数放入到为任务队列,此时执行栈清空
.catch(function() {
// 13、开启定时器,将其放到window上,过0毫秒后将其放入宏任务队列,此时微任务队列清空
setTimeout(function() {
// 16、输出7
console.log(7)
}, 0)
})
最后输出顺序为:1 3 6 4 2 5 7
例子4:
// 1、输出1
console.log(1);
// 2、开启定时器,将其放到window上,过0毫秒后将其放到宏任务队列
setTimeout(function(){
// 10、输出2
console.log(2);
// 11、初始化一个Promise实例
new Promise(function(resolve, reject){
// 12、状态变为成功
resolve();
// 13、绑定then,因为状态已经发生变化,所以立即将这个函数放入到微任务队列
}).then(function(){
// 14、输出3
console.log(3);
})
}, 0);
// 3、初始化一个Promise实例
new Promise(function(resolve, reject){
// 4、输出4
console.log(4);
// 5、状态变为成功
resolve(5);
// 6、绑定then,因为状态已经发生变化,所以立即将这个函数放入到微任务队列
}).then(function(data){
// 9、输出5
console.log(data);
});
// 7、开启定时器,将其放到window上,过0毫秒后将其放到宏任务队列
setTimeout(function(){
// 15、输出6
console.log(6);
}, 0)
// 8、输出7,此时执行栈清空
console.log(7);
最后输出顺序为:1 4 7 5 2 3 6
例子5:
// 1、输出1
console.log(1);
// 2、开启定时器,将其放到window上,过0毫秒后将其放到宏任务队列
setTimeout(function(){
// 16、输出2
console.log(2);
// 17、初始化一个Promise实例
new Promise(function(resolve, reject){
// 18、状态变为成功
resolve();
// 19、绑定then,因为状态已经发生变化,所以立即将这个函数放入到微任务队列
}).then(function(){
// 20、输出3
console.log(3)
})
}, 0);
// 3、初始化一个Promise实例
new Promise(function(resolve, reject){
// 4、输出4
console.log(4);
// 5、状态变为成功
resolve(5);
// 6、绑定then,因为状态已经发生变化,所以立即将这个函数放入到微任务队列
}).then(function(data){
// 9、输出5
console.log(data);
// 10、初始化一个Promise实例
new Promise(function(resolve, reject){
// 11、输出6
console.log(6);
// 12、状态变为成功
resolve();
// 13、绑定then,因为状态已经发生变化,所以立即将这个函数放入到微任务队列
}).then(function(){
// 14、输出7
console.log(7);
// 15、开启定时器,将其放到window上,过0毫秒后将其放到宏任务队列
setTimeout(function(){
// 22、输出8
console.log(8);
}, 0);
})
});
// 7、开启定时器,将其放到window上,过0毫秒后将其放到宏任务队列
setTimeout(function(){
// 21、输出9
console.log(9);
}, 0);
// 8、输出10,此时执行栈清空
console.log(10);
最后输出顺序为:1 4 10 5 6 7 2