likes
comments
collection
share

一文点透,串起事件循环、同步异步与宏任务微任务

作者站长头像
站长
· 阅读数 89

我报名参加金石计划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)

我们刚才把上面的三个搞清楚了,接下来的事件循环就十分好理解了。

事件循环执行顺序

  1. 执行栈 → 执行同步代码
  2. 异步代码 → 宿主环境
  3. 执行栈同步代码执行完毕 ⇒ 在任务队列中寻找函数代码,若有则推送到执行栈调用函数。
  4. 执行完毕执行栈中的任务后 ⇒ 在任务队列中寻找函数代码,若有则推送到执行栈调用函数

第三步和第四步中的不断循环就叫做事件循环

这个时候有部分同学看到这里就开始迷迷糊糊了,这是什么事件循环?不就是宏任务和微任务的循环么?那为什么又是事件循环呢?

我的回答是:还真是!

一文点透,串起事件循环、同步异步与宏任务微任务 上图可以清晰的展示了我们的事件循环

事件循环是执行栈在任务队列里反复询问的循环。


所以JavaScript这个时间管理大师有一个基于事件循环并发模型,事件循环负责执行代码、收集和处理事件以及执行队列中的子任务

一文点透,串起事件循环、同步异步与宏任务微任务

悟道以及注意点

setTimeout这样子计时能准确么?

扫盲时间到:真不能!

函数 setTimeout(fun,delay)接受两个参数:待加入队列的消息和一个时间值(可选,默认为 0)。这个时间值代表了消息被实际加入到队列的最小延迟时间。如果队列中没有其它消息并且栈为空,在这段延迟时间过去之后,消息会被马上处理。但是,如果有其它消息,setTimeout消息必须等待其它消息处理完。因此第二个参数仅仅表示最少延迟时间,而非确切的等待时间。

所以哪怕delay = 0 也没法直接处理,因为先要等同步代码,再要看队列有无其他消息需要先被处理。

永不阻塞

JavaScript 的事件循环模型与许多其他语言不同的一个非常有趣的特性是,它永不阻塞。 处理 I/O 通常通过事件和回调来执行,所以当一个应用正等待一个 IndexedDB 查询返回或者一个 XHR 请求返回时,它仍然可以处理其它事情,比如用户输入。

由于历史原因有一些例外,如 alert 或者同步 XHR,但应该尽量避免使用它们。注意,例外的例外也是存在的(但通常是实现错误而非其它原因)。

写在最后

创作不易,如果本文对你有所帮助,点赞评论关注三连是对博主最大的鼓励,如果博主有理解不到位的地方,也感谢指出纠正博主的错误。

转载自:https://juejin.cn/post/7148359318977380388
评论
请登录