宏任务和微任务、事件循环
JavaScript是单线程的,也就是说,同一个时刻,JavaScript只能执行一个任务,其他任务只能等待。
为什么JavaScript是单线程的
js是运行于浏览器的脚本语言,因其经常涉及操作dom,如果是多线程的,也就意味着,同一个时刻,能够执行多个任务。
试想,如果一个线程修改dom,另一个线程删除dom,那么浏览器就不知道该先执行哪个操作。
所以js执行的时候会按照一个任务一个任务来执行。
为什么任务要分为同步任务和异步任务
试想一下,如果js的任务都是同步的,那么遇到定时器、网络请求等这类型需要延时执行的任务会发生什么?
页面可能会瘫痪,需要暂停下来等待这些需要很长时间才能执行完毕的代码
所以,又引入了异步任务。
-
同步任务:同步任务不需要进行等待可立即看到执行结果,比如console
-
异步任务:异步任务需要等待一定的时候才能看到结果,比如setTimeout、网络请求
宏任务和微任务
异步任务,又可以细分为宏任务和微任务。下面列举目前学过的宏任务和微任务。
宏任务、微任务的执行顺序
执行顺序:先执行同步代码,遇到异步宏任务则将异步宏任务放入宏任务队列中,遇到异步微任务则将异步微任务放入微任务队列中,当所有同步代码执行完毕后,再将异步微任务从队列中调入主线程执行,微任务执行完毕后再将异步宏任务从队列中调入主线程执行,一直循环直至所有任务执行完毕。
举个例子:
比如去银行办理业务排队,每一个办理业务的人就是一个个宏任务,当宏任务张三在柜台办理业务时,其它任务都需等待,当一个宏任务张三办理业务结束时,柜台职员询问他还要不要办理一张信用卡或者其他理财产品,那么这些询问的其它业务就是微任务,如果他还有其他业务,则其他人就需要等待张三办理完,也就是后边的宏任务还需要等待张三办理完微任务之后。
在当前的微任务没有执行完成时,是不会执行下一个宏任务的。
事件循环(Event Loop)
事件循环比较简单,它是一个在 "JavaScript 引擎等待任务","执行任务"和"进入休眠状态等待更多任务"这几个状态之间转换的无限循环。
引擎的一般算法:
-
当有任务时:
-
从最先进入的任务开始执行。
-
-
休眠直到出现任务,然后转到第 1 步。
简单概括一下事件循环,就是
1.执行宏任务队列中第一个任务,执行完后移除它
2.执行所有的微任务,执行完后移除它们
3.执行下一轮宏任务(重复步骤2)
如此循环就形成了event loop,其中,每轮执行一个宏任务和所有的微任务
案例题分析
例题1:
console.log(1)
setTimeout(function() {
console.log(2)
}, 0)
const p = new Promise((resolve, reject) => {
resolve(4)
})
p.then(data => {
console.log(data)
})
console.log(3)
下边分析一下执行顺序:
首先浏览器执行js进入第一个宏任务进入主线程, 遇到 console.log(1) 直接执行输出外层宏事件1
遇到 setTimeout 分发到宏任务队列中...
遇到 Promise,但由于new Promise没有输出事件,接着执行遇到.then
• 执行then 被分发到微任务Event Queue中
遇到最下边consile.log(3) 直接执行输出3
宏任务执行结束,开始执行微任务 打印 data 的值 4
微任务执行完毕,执行第二轮宏事件,打印setTimeout里面内容 2
// 所以最终输出的结果顺序是: 1 3 4 2
例题2:
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)
首先浏览器执行js进入第一个宏任务进入主线程, 直接输出打印console.log(1)
遇到setTimeout 分发到下一个宏任务队列。。。
遇到new Promise 输出打印console.log(5) 遇到.then 被分发到微任务Event Queue中...
遇到第二个setTimeout 分发到下一个宏任务队列。。。
遇到console.log(10) 直接输出打印
第一轮宏任务执行 结果是 1 5 10
接着开始执行第一轮中的微任务.then 输出打印console.log(6)
•执行第二轮宏事件,执行setTimeout
•先执行主线程宏任务,在执行微任务,打印'2,3,4'
•在执行第二个setTimeout,同理打印 '7,8,9'
//最终执行的结果顺序为: 1 5 10 6 2 3 4 7 8 9
例题3:
new Promise((resolve, reject) => {
resolve(1)
new Promise((resolve, reject) => {
resolve(2)
}).then(data => {
console.log(data)
})
}).then(data => {
console.log(data)
})
console.log(3)
// 3 2 1
例题4:
console.log(1);
setTimeout(function () {
console.log(2)
}, 1000);
Promise.resolve()
.then(function () {
console.log(3);
})
.then(function () {
console.log(4);
})
async function errorFunc() {
try {
await Promise.reject('error!!!')
} catch (e) {
console.log(5);
}
console.log(6);
return Promise.resolve('errorFunc success')
}
errorFunc().then((res) => console.log(7))
console.log(8);
//1 8 3 5 6 4 7 2
例题5:
setTimeout(() => {
console.log(1)
}, 0)
new Promise((resolve, reject) => {
console.log(2)
resolve('p1')
new Promise((resolve, reject) => {
console.log(3)
setTimeout(() => {
resolve('setTimeout2')
console.log(4)
}, 0)
resolve('p2')
}).then(data => {
console.log(data)
})
setTimeout(() => {
resolve('setTimeout1')
console.log(5)
}, 0)
}).then(data => {
console.log(data)
})
console.log(6)
//2 3 6 p2 p1 1 4 5