# 事件循环 Event Loop
本质是:
1. 作为单线程js对于异步事件的处理机制
2. 或者可以说是 只有一个主线程js的处理逻辑
3. 如何保证主线程, 有序并高效 或非阻塞 的处理呢? => 事件循环机制 Event Loop
4. 异步任务也是有优先级的,分为 宏任务 MacroTask, 微任务 MicroTask
你也可能会碰到以下问题:
- js如何处理 同步 和 异步?
- 什么是事件循环机制?
- 异步的宏任务和微任务区别?
- 执行流程优先级又是什么?
1- 单线程 js 按照语句出现顺序执行
一道题看懵你, 这个输出结果并不是按照语句顺序阿~
setTimeout(function(){
console.log('定时器开始啦')
});
new Promise(function(resolve){
console.log('马上执行for循环啦');
for(var i = 0; i < 10000; i++){
i == 99 && resolve();
}
}).then(function(){
console.log('执行then函数啦')
});
console.log('代码执行结束');
结果:
- 马上执行for循环啦
- 代码执行结束
- 执行then函数啦
- 定时器开始啦
2- 事件循环 Event Loop
- js是单线程, 那么相当于只有一个柜台窗口的银行, 要一个个顺序的处理业务。前面一个任务是要存款1000w 办理时间长, 后面存款10w的就要等着。
- 怎么能更高效的处理同步和异步的任务呢?
- 那么问题是: 如果浏览器加载一张图片非常的慢需要10s, 难道要等着其下载完,才能执行点击 或 其他操作吗?
- 事件循环机制: 本质上是js对异步处理机制。
任务:
- 同步任务
- 异步任务
- 同步和异步的任务分别进入不同的执行场所。
- 同步直接在主线程执行,异步的回调 则去小黑屋,等待着,时机到了才会被运行。
- 当同步任务在主线程中全部执行完毕后,再去事件队列中执行 异步回调的函数,并进入主线程执行。
- 上述过程不断重复。叫作 事件循环机制。
2.1- 例子1
axios.get().then(() => console.log(1))
console.log(2)
// 执行顺序: axios => 输出2 => 输出1
- axios 请求
- 异步回调函数 放入异步队列中
- 执行console.log(2) 主线程空 则执行异步队列中的函数
- console.log(1) 被执行
2.2- 例子2 setTimeout
你会发现console.log('延时3秒'); 输出的不是3s, 是5s, 超过3s.
<script>
const sleep = time => {
let startTime = new Date().getTime() + parseInt(time, 10)
while (startTime > new Date().getTime()) {}
}
setTimeout(() => {
console.log('延时3秒');
}, 3000)
// 休眠5s
sleep(5000)
</script>
- setTimeout执行, 3s后, 将 回调函数 放到 异步队列中 [3s => '延时3秒']
- 执行sleep函数, 停止5s
当sleep函数执行到3s的时候, 异步函数返回值可执行, 但是因为sleep占用主线程, 只能等待 - 5s后, 主线程空了, 执行输出 '延时3秒'
- 所以,此时延迟是大于5s
3- 宏任务 和 微任务
- 宏任务 macro task: script代码, setTimeout, setInterval
- 微任务 micro task: Promise.then, process.nextTick
异步的事件队列 还分是去 宏任务 还是去 微任务 !
- 同步
- 微任务异步队列(Micro Event Queue)
- 宏任务异步队列(Macro Event Queue)
<script>
setTimeout(() => {
console.log('setTimeout 宏任务: 异步队列')
})
console.log('宏任务: 同步队列 = 1')
new Promise(resolve => {
resolve('666')
console.log('promise 任务先执行, then才是微任务处理')
}).then(data => {
console.log('promise.then 微任务执行', data)
})
console.log('宏任务: 同步队列 = 2')
</script>
- 宏任务: 同步队列 = 1
- promise 任务先执行, then才是微任务处理
- 宏任务: 同步队列 = 2
- promise.then 微任务执行 666
- setTimeout 宏任务: 异步队列
- setTimeout 先执行, 将回调函数放到 异步队列中, 等待执行 MacroQueue = [0s=> 'setTimeout 宏任务: 异步队列']
- 输出 => '宏任务: 同步队列 = 1'
- new Promise立即被执行
- 输出 => 'promise 任务先执行, then才是微任务处理'
- then 放入 MicroQueue = [then=> 'promise.then 微任务执行' + data ]
- 输出 => '宏任务: 同步队列 = 2'
- 主线程空了,可以执行异步的事情。
- 先去执行 MicroQueue, 再去执行MacroQueue
- 输出 => promise.then 微任务执行 666
- 输出 => setTimeout 宏任务: 异步队列
4- 宏任务 和 微任务 执行顺序?
宏任务 macro task: script代码, setTimeout, setInterval
微任务 micro task: Promise.then, process.nextTick
- 同步任务线进入主线程执行
- 异步处理还分为: macro宏任务, micro 微任务
- micro 微任务优先级优于macro宏任务
- 优先级: 同步任务 > micro 微任务 > macro宏任务
// 同步, 异步宏任务macro, 异步微任务micro
// 同步 > 异步微任务micro > 异步宏任务macro
console.log('1');
// as setTimeout1
setTimeout(function() {
console.log('2');
// as process2
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
// promise.then__setTimeout1
console.log('5')
})
})
// as process1
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
// as setTimeout2
setTimeout(function() {
console.log('9');
// as process3
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
// promise.then__setTimeout2
console.log('12')
})
})
/**
结果
1
7
6
8
2
4
3
5
9
11
10
12
*/
步骤分析
1. 输出 => 1
2. 放入宏任务 异步队列中 MacroEventQueue = [setTimeout1]
3. 放入微任务 异步队列中 MicroEventQueue = [process1]
4. promise先执行
输出 => 7
将promise.then 放入微任务 异步队列中 MicroEventQueue = [process1, promise.then]
5. 放入宏任务 异步队列中 MacroEventQueue = [setTimeout1, setTimeout2]
6. 此时同步已经执行完了, 主线程空闲, 先执行MicroEventQueue, 再执行MacroEventQueue
7. 先执行MicroEventQueue = [process1, promise.then]
8. 输出 => 6
9. 输出 => 8
10. MicroEventQueue已空, 执行MacroEventQueue
11. MacroEventQueue = [setTimeout1, setTimeout2]
12. 输出 => 2
MicroEventQueue = [process2]
Promise立刻执行
输出 => 4
Promise.then放到 MicroEventQueue = [process2, promise.then__setTimeout1]
13. 此时剩下 MacroEventQueue = [setTimeout2] MicroEventQueue = [process2, promise.then__setTimeout1]
MicroEventQueue不为空, 先执行
输出 => 3
输出 => 5
此时剩下 MacroEventQueue = [setTimeout2] MicroEventQueue 空
14. MacroEventQueue = [setTimeout2]
输出 => 9
MicroEventQueue = [process3]
Promise立刻执行
输出 => 11
MicroEventQueue = [process3, promise.then__setTimeout2]
15. 此时 MacroEventQueue 空, MicroEventQueue = [process3, promise.then__setTimeout2]
输出 => 10
输出 => 12
5- Recap
- js单线程,同步阻塞
- 同步和异步。同步优于异步在主线程中执行, 只有主线程空闲, 异步才能被执行
- 事件循环机制,异步的解决方案 / 机制
- 事件循环机制 => macro 宏任务 和 micro任务
- 最后: 优先级 同步 > micro任务 > macro 宏任务
6- 整体总结
js是单线程, 也就是说只有一个 主线程执行
不恰当例子有助于理解:
银行就一个柜台, 好多人去排队。
每个人的业务不同, 有人就是交水电费, 办理存款业务的人花费时间多些
如何提高银行的办事儿效率?
规定, 我们暂定按照处理时间来算优先级
1. 交水电费优先级更高些 (花费10ms)
2. 办理存款业务的人低优先级 (花费1s)
- 存款10w (花费10s)
- 存款1个亿 (花费100s)
按照花费的时间算优先级
- 交水电费 (同步) 直接在主线程运行
- 办理存款业务 另外开个小窗口, 毕竟是VIP大客户, 处理存款
- 存款10w 放到 Micro Event Queue 微任务队列中
- 存款1个亿 放到 Macro Event Queue 宏任务队列中
优先级: 同步任务 > Micro Event Queue 异步微任务队列 > Macro Event Queue 异步宏任务队列
事件循环: 一旦发现优先级比自己高的, 要去执行高的, 让出主线程, 等前一个优先级任务空了, 再执行后面的。
如何管理 同步和异步 在主线程的处理逻辑?
- 同步: 优先级更高在主线程处理掉, 异步只能等同步处理完, 再处理.
Event Loop 事件循环
- 异步: 也分369等, 宏任务和微任务之分.
微任务: promise.then, process.nextTick
宏任务: setTimeout, setInterval, axios回调, 正常js逻辑等
- 优先级: 同步 > 异步(微任务) > 异步(宏任务)
7 - 题巩固一下
7.1 - 题1
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
- 输出 => script start
- setTimeout放到macro queue 等到主线程空运行
- Promise.resolve()里的立刻执行,then后的都放到micro queue 等待运行
- 输出 => script end
- micro queue 优先 macro queue
- 输出 => promise1
- 输出 => promise2
- 输出 => setTimeout
7.1 - 题2
注意async, await 的用法 和 promise的关系
console.log('script start')
async function async1() {
await async2() // await = 立即执行,后面的code, 相当于放到promise.then(...)
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()
setTimeout(function() {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function() {
console.log('promise1')
})
.then(function() {
console.log('promise2')
})
console.log('script end')
- 输出 => script start
- async1() 立即执行
- await async2() 立即执行, 后面的code, 相当于放到promise.then(...)
// 重点
async function f() {
await p() // p立即执行, 后面的代码,相当于放到then里
console.log(1)
}
// 相当于
function f () {
return new Promise(() => {
resolve(p())
}).then(() => console.log(1))
}
- 输出 => script start
- 输出 => async2 end await async2() = new Promise(xxx) 一样会立刻执行
- console.log('async1 end') 被放到micro queue中 = await下一行 相当于 promise.then后内容
- setTimeout 放到 macro queque中
- 输出 => Promise, 两个then的内容放到micro queue中
- 输出 => script end
- 执行 micro queue
- 输出 => async1 end
- 输出 => promise1
- 输出 => promise2
- 执行 macro queue
- 输出 => setTimeout
7.3 - 题3
console.log('start')
setTimeout(() => {
console.log('timer1')
Promise.resolve().then(function() {
console.log('promise1')
})
}, 0)
setTimeout(() => {
console.log('timer2')
Promise.resolve().then(function() {
console.log('promise2')
})
}, 0)
Promise.resolve().then(function() {
console.log('promise3')
})
console.log('end')
- 输出 => start
- setTimeout 进入 marco queue
- setTimeout 进入 marco queue
- Promise.resolve()立即执行,then放到 micro queue
- 输出 => end
- 先执行 micro queue
- 输出 => promise3
- marco queue执行
- 输出 => timer1
- Promise.resolve()立即执行,then放到 micro queue
- 因为micro queue 优先级 > marco queue
- 输出 => promise1 此时micro queue已清空
- marco queue执行
- 输出 => timer2
- 输出 => promise2
- 如果你用浏览器去执行,下面即使答案。同步 > micro任务 > macro 宏任务
- 如果你用Node11+版本,和浏览器行为一致。同步 > micro任务 > macro 宏任务
- 如果你是Node10以下版本,输出为: 当macro为空才去执行micro。
start
end
promise3
timer1
timer2
promise1
promise2