Event Loop(事件循环)
js是单线程的,一次只能执行一段代码。单线程会导致很多任务需要排队,一个个去执行,如果此时某个任务执行时间太长,就会出现阻塞,为了解决这个问题,js引入了事件循环机制。
为什么要区分宏任务和微任务?
js是单线程的,但是分同步异步
微任务和宏任务皆为异步任务,它们都属于一个队列
宏任务:script(整体代码)、setTimeout、setInterval、I/O、UI、 renderingsetImmediate(Node环境)
微任务:promise.then、Object.observe、MutationObserver、process.nextTick(Node环境)
先执行同步再执行异步,异步遇到微任务,先执行微任务,执行完后如果没有微任务,就执行下一个宏任务,如果有微任务,就按顺序一个一个执行微任务
任务优先级:
先执行同步代码,遇到宏任务则将宏任务放入宏任务队列中,遇到微任务则将微任务放入微任务队列中,当所有同步代码执行完毕后,再将异步微任务从队列中调入主线程执行,微任务执行完毕后再将宏任务从队列中调入主线程执行,一直循环直至所有任务执行完毕。
这里容易产生一个错误的认识:就是微任务先于宏任务执行。实际上是先执行同步任务然后在执行异步任务,异步任务是分宏任务和微任务两种的。
看一个例子,最后结果输出啥?
setTimeout(() => {
console.log(1)
}, 0)
new Promise((resolve) => {
console.log(2)
resolve()
console.log(3)
}).then(() => {
console.log(4)
})
console.log(5)
分析:
1、遇到 setTimout(宏任务),放入宏任务队列中
2、遇到 new Promise,在实例化的过程中所执行的代码都是同步进行的,所以输出 2 和 3
3、Promise.then(微任务),将其放入微任务队列中
4、遇到同步任务 console.log(5) 输出5,主线程中同步任务执行完
5、从微任务队列中取出任务到主线程中,输出 4,微任务队列为空
6、从宏任务队列中取出任务到主线程中,输出 1,宏任务队列为空,事件循环结束
最后结果为:2 3 5 4 1
例子2:
setTimeout(() => {
new Promise(resolve => {
resolve()
}).then(() => {
console.log(1)
})
console.log(2)
})
new Promise(resolve => {
resolve()
console.log(3)
}).then(() => {
console.log(4)
Promise.resolve().then(() => {
console.log(5)
}).then(() => {
Promise.resolve().then(() => {
console.log(6)
})
})
})
console.log(7)
分析:
- 遇到setTimeout(宏任务),将 console.log(2) 放入宏任务队列中;
- 遇到 new Promise,在实例化的过程中所执行的代码都是同步进行的,所以输出 3;
- 而 Promise.then(微任务),将其放入微任务队列中
- 遇到同步任务 console.log(7),输出 7 ;主线程中同步任务执行完
- 从微任务队列中取出任务到主线程中,输出 4 ,此微任务中又有微任务,Promise.resolve().then(微任务a).then(微任务b),将其依次放入微任务队列中;
- 从微任务队列中取出任务 a 到主线程中,输出 5;
- 从微任务队列中取出任务 b 到主线程中,任务 b 又注册了一个微任务 c,放入微任务队列中;
- 从微任务队列中取出任务 c 到主线程中,输出 6,微任务队列为空
- 从宏任务队列中取出任务到主线程,此任务中注册了一个微任务 d,将其放入微任务队列中,接下来遇到输出 2,宏任务队列为空
- 从微任务队列中取出任务 d 到主线程,输出 1,微任务队列为空
最后结果为:3 7 4 5 6 2 1
例子3:
console.log(1)
setTimeout(function () {
console.log(2)
new Promise(function (resolve) {
console.log(3)
resolve()
}).then(function () {
console.log(4)
})
})
new Promise(function (resolve) {
console.log(5)
resolve()
}).then(function () {
console.log(6)
})
setTimeout(function () {
console.log(7)
new Promise(function (resolve) {
console.log(8)
resolve()
}).then(function () {
console.log(9)
})
})
console.log(10)
分析:
- 遇到同步任务 console.log(1) 输出1;
- 遇到 setTimeout(宏任务),放入宏任务队列中;
- 遇到 Promise,new Promise 在实例化的过程中所执行的代码都是同步进行的,所以输出 5,所以接着执行遇到 .then;
- 执行 .then(微任务),被分发到微任务 Event Queue 中;
- 遇到 setTimeout,宏任务,放入宏任务队列中;
- 遇到同步任务 console.log(10) 输出10,主线程中同步任务全部执行完;
- 从微任务队列中取出任务到主线程中,输出6;
- 在从宏任务队列中取出任务到主线程中,执行第一个setTimeout,输出2,3,4(在宏任务中执行同步,同步,微任务);
- 在执行第二个 setTimeout,输出7,8,9;
最后结果为:1,5,10,6,2,3,4,7,8,9
例子4:
new Promise((resolve) => {
resolve(1)
new Promise((resolve) => {
resolve(2)
}).then(data => {
console.log(data)
})
}).then(data => {
console.log(data)
})
console.log(3)
分析:
- 遇到 new Promise 没有输出,往下走,然后又遇到了 new Promise 也没有输出,接着往下走,遇到 then(微任务)被分发到任务队列中,里面的走完了接着走外面的 then 同样也是分发到任务队列中;
- 接着遇到同步任务 console.log(3) 输出3,主线程中同步任务执行完毕;
- 从微任务队列中取出任务添加到主线程中,输出 2 和 1,微任务执行完毕,任务队列为空;
最后结果为:3 2 1