一、Event Loop是什么?
JavaScript是一门单线程语言,它的异步和多线程的实现,是通过Event Loop(也称为事件循环)来实现的。Event Loop是一个执行机制,浏览器和Node.js基于各种不同的技术实现了各自的EventLoop。本文重点来讲述基于浏览器的Event Loop。
二、调用栈
咱都知道JavaScript分为同步任务和异步任务。Event Loop刚开始时会将同步任务一行一行执行,遇到函数调用,会把它压入调用栈(call stack)中,当函数执行完毕后会从栈中弹出,根据栈后进先出的顺序执行同步代码。
举个栗子:
function func1() {
console.log(1);
}
function func2() {
console.log(2);
func1();
console.log(3);
}
func2();
Step1:
func2()压入调用栈中并执行:call stack[ func2(); ]
Step2: console.log(2);
Step3:
func1()压入调用栈中并执行:call stack[ func1(); func2(); ]
Step4: console.log(1);
Step5:
func1()执行完毕并弹出调用栈:call stack[ func2(); ]
Step6: console.log(3);
Step7:
func2()执行完毕并弹出调用栈:call stack[ ]
输出顺序:2 1 3
三、任务队列
任务队列可以看作是JavaScript另开辟的另一个副线程,JavaScript中的常见的异步任务如:setTimeout、setInterval、I/O操作、DOM事件、Promise的回调(then、catch、finally…)等,其回调函数会入队到任务队列中,等待同步任务的执行完毕,也就是调用栈为空的时候,再去依次执行异步任务。按照队列先进先出的的顺序执行回调。
而任务队列又可分为宏任务队列与微任务队列,在任务队列中,Event Loop的执行顺序为:
1.从微任务队列(microtask queue)中取出位于队首的任务,放入调用栈执行,微任务队列长度减1
2.不断执行1的操作(如执行过程中产生微任务将继续放入队尾等待执行),以此类推,直到微任务队列为空
3.微任务队列执行完毕,此时microtask queue为空队列,调用栈call stack也为空
4.取出宏任务队列(macrotask queue)的队首任务,放入调用栈执行,宏任务队列长度减1
5.不断执行4的操作,如执行过程中产生微任务将放入microtask queue队列并开始掉头执行微任务
6.执行1、2操作直到微任务队列再次为空,继续执行宏任务
7.直到所有任务执行完毕,调用栈call stack、微任务队列microtask queue、宏任务队列macrotask queue都为空
1.宏任务队列
常见的宏任务如:setTimeout、setInterval、I/O操作
举个栗子:
function func1() {
console.log(1);
}
function func2() {
setTimeout(() => {
console.log(2);
}, 0);
func1();
console.log(3);
}
func2();
Step1:
func2()压入调用栈中并执行:call stack[ func2(); ]
Step2:
setTimeout()压入栈中,其内部回调函数callback进入宏任务队列,并弹出定时器setTimeout()
call stack[ setTimeout(); func2(); ]
macrotask queue[ callback ]
Step3:
func1()压入调用栈中并执行:
call stack[ func1(); func2(); ] **
macrotask queue[ callback ]
Step4: console.log(1);
Step5:
func1()执行完毕并弹出调用栈:
call stack[ func2(); ]
macrotask queue[ callback ]
Step6: console.log(3);
Step7:
func2()执行完毕并弹出调用栈:
call stack[ ]
macrotask queue[ callback ]
Step8:
此时调用栈为空,因无微任务要执行,从宏任务队列中取出回调函数入调用栈执行:
console.log(2);
call stack[ ]
macrotask queue[ ]
输出顺序:1 3 2
2.微任务队列
常见的微任务如:promise的回调(then、catch、finally…)
举个栗子:
function func1() {
console.log(1);
}
function func2() {
new Promise((resolve, reject) => {
resolve(4);
console.log(5);
}).then(data => {
console.log(2);
})
func1();
console.log(3);
}
func2();
Step1:
func2()压入调用栈中并执行:call stack[ func2(); ]
Step2:
new Promise压入栈中并执行其内部同步代码:
call stack[ new Promise; func2(); ]
Step3:
Promise内部同步代码resolve(4)、console.log(5)依次压入栈中并执行:
console.log(5)
call stack[ new Promise; func2(); ]
Step4:
promise.then中的回调函数callback进入微任务队列,并弹出new Promise构造函数:
call stack[ func2(); ]
microtask queue[ callback ]
Step5:
func1()压入调用栈中并执行:
call stack[ func1(); func2(); ] **
microtask queue[ callback ]
Step4: console.log(1);
Step5:
func1()执行完毕并弹出调用栈:
call stack[ func2(); ]
microtask queue[ callback ]
Step6: console.log(3);
Step7:
func2()执行完毕并弹出调用栈:
call stack[ ]
microtask queue[ callback ]
Step8:
此时调用栈为空,从微任务队列中取出回调函数进入调用栈执行:
console.log(2);
call stack[ ]
microtask queue[ ]
输出顺序:5 1 3 2
四、案例分析
由同步与异步任务的执行顺序描述以及以上两例分析,相信已经对同步任务和异步任务的执行顺序有了基本的一定的熟悉,下面将结合同步与异步任务(结合宏任务与微任务),根据上文执行顺序的描述,来练习一下叭!
console.log(1);
setTimeout(() => {
console.log(2);
Promise.resolve().then(() => {
console.log(3)
});
});
new Promise((resolve, reject) => {
console.log(4)
resolve(5)
}).then((data) => {
console.log(data);
})
setTimeout(() => {
console.log(6);
})
console.log(7);
输出顺序:1 4 7 5 2 3 6
结合先同后异,先微后宏,宏中带微掉头微的顺序分析,你做对了吗~~
总结
希望大家看了本篇文章都有收获 …有不解的地方评论区秒答复,简单阐述了一下该知识点,有错误的话,望斧正!!