一文点透,串起事件循环、同步异步与宏任务微任务
我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第2篇文章,点击查看活动详情
写在前面:
某个月黑风高的夜晚,我搞Java的室友龙哥问了我一个问题:
我的回答:
好吧,如果要讲前端的同步异步的话,那不得不说到经典面试题,事件循环与宏任务与微任务,本文一文带你了解时间管理大师JavaScript先生基于事件循环的并发模型以及同步异步,宏任务和微任务。本文中个人理解如有错误欢迎大神指出。
概念了解
在了解事件循环之前,我们首先要知道一些东西,例如JavaScript是一门单线程的语言,这是JS的一个特点,设计之初是如此未来想必也不会改变
为什么JS是单线程?
举一个简单的栗子来说,我们都知道JS可以用来控制DOM元素,假设我有两段JS代码,
(1)是用来生成一个DOM元素
(2)是用来删除该DOM元素
当我的JS是多线程时,如果(2)先执行了,可是根本就没有该DOM元素,这个时候逻辑上就必然有错误,所以JS只能是单线程的,先生成DOM才能删除DOM。 (虽然这个问题是可以通过加锁或者一些其他方法解决(问题总有方法解决),但是可能会得不偿失。)
同步任务和异步任务
当JS是单线程的时候,此时在使用JS时又会被一个事情给难到,当我有一段代码是一个耗时任务或事件时
//耗时任务
console.log(1)
setTimeout(()=>{
console.log(2)
},2000)
console.log(3)
//事件
console.log(1)
button.addEventListener('click',() => {console.log(2)})
console.log(3)
如果一定要等待该代码执行完毕才能执行接下来的代码的话,那必然会造成程序假死,整个程序被阻塞,使用体验不佳等问题,这是一个极大的缺陷,于是javaScript
语言将任务的执行模式分成了两种。同步任务模式(Synchronous
)和异步任务模式(Asynchronous
)。
常见异步代码:setTimeout
,setInterval
,Ajax/Fetch
,事件绑定
等耗时任务
注:Promise
是同步的,但是.then
,.catch
是异步的
重点:
同步我要等,异步你随便。
- 同步代码:立即放入JS引擎(主线程)执行,原地等待结果
- 异步代码:先放入宿主环境(broswer/Node)不原地等待结果,不阻塞主线程继续往下执行,结果在将来执行
异步任务分为 宏任务(macrotask) 微任务 (microtask)
宏任务(macrotask) 与 微任务 (microtask)
微任务
一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前
常见的微任务有:
- Promise.then .catch
- MutaionObserver
- Object.observe(已废弃;Proxy 对象替代)
- process.nextTick(Node.js)
宏任务
宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合
常见的宏任务有:
- script (可以理解为外层同步代码)
- setTimeout/setInterval
- UI rendering/UI事件
- postMessage、MessageChannel
- setImmediate、I/O(Node.js)
宏任务 → 执行结束
如图所示:
事件循环(EventLoop)
我们刚才把上面的三个搞清楚了,接下来的事件循环就十分好理解了。
事件循环执行顺序
- 执行栈 → 执行同步代码
- 异步代码 → 宿主环境
- 执行栈同步代码执行完毕 ⇒ 在任务队列中寻找函数代码,若有则推送到执行栈调用函数。
- 执行完毕执行栈中的任务后 ⇒ 在任务队列中寻找函数代码,若有则推送到执行栈调用函数
第三步和第四步中的不断循环就叫做事件循环
这个时候有部分同学看到这里就开始迷迷糊糊了,这是什么事件循环?不就是宏任务和微任务的循环么?那为什么又是事件循环呢?
我的回答是:还真是!
上图可以清晰的展示了我们的事件循环
事件循环是执行栈在任务队列里反复询问的循环。
所以JavaScript这个时间管理大师有一个基于事件循环的并发模型,事件循环负责执行代码、收集和处理事件以及执行队列中的子任务
悟道以及注意点
setTimeout这样子计时能准确么?
扫盲时间到:真不能!
函数 setTimeout(fun,delay)
接受两个参数:待加入队列的消息和一个时间值(可选,默认为 0)。这个时间值代表了消息被实际加入到队列的最小延迟时间。如果队列中没有其它消息并且栈为空,在这段延迟时间过去之后,消息会被马上处理。但是,如果有其它消息,setTimeout
消息必须等待其它消息处理完。因此第二个参数仅仅表示最少延迟时间,而非确切的等待时间。
所以哪怕delay = 0
也没法直接处理,因为先要等同步代码,再要看队列有无其他消息需要先被处理。
永不阻塞
JavaScript 的事件循环模型与许多其他语言不同的一个非常有趣的特性是,它永不阻塞。 处理 I/O 通常通过事件和回调来执行,所以当一个应用正等待一个 IndexedDB
查询返回或者一个 XHR
请求返回时,它仍然可以处理其它事情,比如用户输入。
由于历史原因有一些例外,如 alert
或者同步 XHR,但应该尽量避免使用它们。注意,例外的例外也是存在的(但通常是实现错误而非其它原因)。
写在最后
创作不易,如果本文对你有所帮助,点赞评论关注三连是对博主最大的鼓励,如果博主有理解不到位的地方,也感谢指出纠正博主的错误。
转载自:https://juejin.cn/post/7148359318977380388