更多个人博客:(https://github.com/zenglinan/blog)
如果对你有帮助,欢迎star。
理解事件循环机制可以更好地理解 JS 中的异步代码
要理解事件循环机制, 要先理解一些前置知识
一. 前置知识
61. 栈
栈是一种后进先出的数据结构, 栈只支持对栈顶进行数据的插入和删除。
你可以想象成一沓书, 先放的被压在底下, 后放的可以最先拿出来。
2. 队列
队列是一种先进先出的数据结构, 队列只支持对队尾进行数据插入, 对对头进行数据删除。
可以想象成排队, 先排队的人可以先完成并离开, 同时只能从队的尾巴进行排队。
3. 浏览器的调用栈
JS 有一个调用栈, 也叫执行栈。当函数执行的时候, 会将函数推入调用栈中, JS 主线程会执行调用栈顶的代码, 当函数执行完毕后出栈。
来看一段代码:
function a(){
console.log('a')
return b()
}
function b(){
console.log('b')
return c()
}
function c(){
console.log('c')
}
a()
7
4. JS 中的任务队列
前面提到了队列的概念, 在 JS 中, 有专门的任务队列用来存放一些待执行的任务, 这些任务会被通过一些指定的顺序推入调用栈中执行。
任务队列又分宏任务队列和微任务队列,
宏任务
setTimeout、setInterval、script(整个 js 文件代码)、 I/O 操作、UI 渲染等。
微任务
Promise 中的 .then, MutationObserver(html5新特性)
二. 浏览器的事件循环机制
- 最开始, 执行栈为空, 微任务队列为空, 宏任务队列有一个 script 标签(内含整体代码)
- 将第一个宏任务出队, 这里即为上述的 script 标签
- 整体代码执行过程中,
如果是同步代码, 直接执行(函数执行的话会有入栈出栈操作),
如果是异步代码, 会根据任务类型推入不同的任务队列中(宏任务或微任务) - 当执行栈执行完为空时, 会去处理微任务队列的任务, 将微任务队列的任务一个个推入调用栈执行完
- 调用栈为空后, 再出队一个宏任务, 推入调用栈执行
- 调用栈为空时, 去执行一队微任务
- ...往返循环直到宏任务和微任务队列为空
总结一下上述循环机制的特点:
出队一个宏任务 -> 调用栈为空后, 执行一队微任务 -> 更新界面渲染 -> 回到第一步
上面都是空谈概念, 现在让我们看一段代码:
Promise.resolve().then(()=>{
console.log('Promise1')
setTimeout(()=>{
console.log('setTimeout2')
},0)
})
setTimeout(()=>{
console.log('setTimeout1')
Promise.resolve().then(()=>{
console.log('Promise2')
})
Promise.resolve().then(()=>{
console.log('Promise3')
})
},0)
分析一下这个循环过程:
- script 标签(整体代码)入栈
- 执行整体代码过程中先后将异步任务
Promise.resolve().then
和setTimeout
放入微任务和宏任务队列中 - 执行微任务队列的所有任务, 打印
"Promise1"
, 又将里面的异步任务setTimeout
推入宏任务队列中 - 取出第一个宏任务推入栈执行, 打印出
"setTimeout1"
, 同时将内部的异步任务promise.resolve().then
推入微任务队列 - 执行微任务队列中的所有任务, 先后打印出
"Promise2" "Promise3"
- 执行最后一个宏任务, 打印出
"setTimeout2"
以上, 便是浏览器的 event loop
机制。
感谢你看到这里 (▽)
欢迎关注更多个人博客
本文正在参与“写编程博客瓜分千元现金”活动,关注公众号“饥人谷”回复“编程博客”参与活动。
网友评论