18 | 宏任务和微任务:不是所有任务都是一个待遇
该思维导图由 AI 生成,仅供参考
宏任务
- 深入了解
- 翻译
- 解释
- 总结
浏览器消息队列中的宏任务和微任务对代码执行顺序和性能有重要影响。宏任务包括渲染事件、用户交互事件、JavaScript脚本执行事件和网络请求完成等任务,而微任务在实时性和效率之间取得平衡,被广泛应用于MutationObserver、Promise等技术。了解宏任务和微任务的区别对于理解和编写高效、现代化的代码至关重要。本文介绍了宏任务的执行过程和局限性,强调了微任务在满足时间精度要求较高的任务方面的优势。此外,文章还详细介绍了微任务的产生和执行时机,以及MutationObserver技术方案的演化历程,最终指出MutationObserver采用了“异步+微任务”的策略,有效地权衡了实时性和执行效率的问题。通过本文的总结,读者可以快速了解宏任务和微任务的概念、微任务的工作流程以及MutationObserver技术方案的优势,为他们理解现代化代码执行机制提供了重要参考。
《浏览器工作原理与实践》,新⼈⾸单¥59
全部留言(84)
- 最新
- 精选
- 阿桐买过不少专栏,每一篇都紧跟并且会反复看的目前只有这一个。一方面懒另一方面是好的系统性的学习资料不多,所以以前很少关注偏底层原理性的东西,所以这个专栏学习起来是既收获满满有时也不乏一额头问号。 这里有 2 个问题想向老师请教,希望老师百忙之中能抽空解答一下,多谢多谢。 1、之前讲过,在循环系统的一个循环中,先从消息队列头部取出一个任务执行,该任务执行完后,再去延迟队列中找到所有的过期任务依次执行完。那前面这句话和本篇文章的这句话好像有矛盾:"先从多个消息队列中选出一个最老的任务,这个任务称为 oldestTask" 2、”通常情况下,在当前宏任务中的 JavaScript 快执行完成时,也就在 JavaScript 引擎准备退出全局执行上下文并清空调用栈的时候,JavaScript 引擎会检查全局执行上下文中的微任务队列,然后按照顺序执行队列中的微任务。“ 在页面生存周期内,全局执行上下文只有一份并且会一直存在调用栈中,只有当页面被关闭的时候全局执行上下文才会消失。页面都快关闭了,把全局执行上下文中的微任务队列中的任务都执行一遍,好像也没啥意义。系统应该不会做没有意义的事情,所以应该是我对全局执行上下文的某处理解有问题,但我又自查不到。
作者回复: 非常高兴你能提出这些问题。 你有这两个疑问很正常,说明你看得很仔细,之所以你会感到疑惑,主要是我在写作过程中偷懒了。 我先来解答你的第一个问题: 第一段话是WHATWG标准定义的,在WHATWG规范,定义了在主线程的循环系统中,可以有多个消息队列,比如鼠标事件的队列,IO完成消息队列,渲染任务队列,并且可以给这些消息队列排优先级。 但是在浏览器实现的过程中,目前只有一个消息队列,和一个延迟执行队列。 一个是规范,一个是实现,主要我没有在文中强调这点,所以你会产生的这样的疑问。 关于第二个问题解释起来就比较复杂了,涉及到来了V8是怎么执行的了,专栏中的"全局执行上下文"我没有深入分析。所以我偷懒了,把两个稍微有点不同的概念都称为了“全局执行上下文”,要解释清楚这个问题还要牵涉到V8的一个底层逻辑,既然你提出来了,那我就打算在课程结束后,通过加餐的形式来开一讲,讲清楚了这个还能额外地理解 Realm 概念。
2019-09-1711136 - 花儿与少年提问: Mutation Event的回调 是同步的吗?如果是同步的,引擎是怎么做到的? 同步代码执行的时候,还能插入其他代码(mutation 回调)?
作者回复: 要理解这个就得讲观察者模式了,不过展开又是一篇文章,我到时候加餐的时候再来结合观察者模式来讲这个。
2019-09-1729 - coder对于文中一处有疑虑: “第一种是把异步回调函数封装成一个宏任务,添加到消息队列尾部,当循环系统执行到该任务的时候执行回调函数。这种比较好理解,我们前面介绍的 setTimeout 和 XMLHttpRequest 的回调函数都是通过这种方式来实现的。” 第16讲提到了,setTimeout里的延迟任务,是存在一个延迟队列中的。我看精选留言部分老师的回答,提到这个延迟队列实际上是一个hashmap,那么setTimeout的实现还是加到队尾,等到前面的都出队,才执行到这个任务的吗?
作者回复: setTimeout因为是定时任务,设定的时间间隔没有到是不会执行,由此需要一个单独的模块来保存定时器的消息,你可以通过该模块取出到期的任务,我们把这个模块叫延时队列,Chrome内部用了个hashmap保存数据,然后又写了取出到期的任务的策略! 通常情况下,是当渲染主线程在执行完一个正常的任务之后,再判断该模块中是否有到期的任务,如果有取出来执行!
2019-12-2234 - HoSalt老师,通过控制面板中修改的样式是不是不会触发MutationObserver?
作者回复: 不会,因为没有出发js,这个微任务是v8触发的
2020-04-1023 - 夏了夏天老师,我有个疑惑,主线程读取消息队列里的任务的时机是「系统调用栈」的任务执行完毕时还是「JS的调用栈」执行完毕时?
作者回复: js只不是任务的一个过程,这里讲的都是C++层面的,所以也可以说是系统调用栈。 具体的你可以看看加餐5
2019-12-181 - 小智反复读了几次,还是有很多疑问,不同于最初的几篇文章,这里的理论偏多,希望能有更多的案例结合理论分析,才能进一步验证心中理解的
作者回复: 嗯。后面有疑问正常,理论偏多,而且篇幅之间的依赖性比较强。可以把你的问题列出来,答疑的时候我会结合实际列子来分析。
2019-09-1921 - 凭实力写bug执行流程很清楚,也知道了几个队列的关系,看完唯一迷惑的是那里算一个任务,对着performance执行流程就很清晰了
作者回复: 后续有补充内容
2019-11-02 - man-moonth错误和缺失之处烦请老师指正: 1. 执行`p0 = new Promise(executor)`,立即调用`executor()`。依次打印`1`和`rand`,根据`rand > 0.5`判断执行`resolve()`还是`reject()`,分别决定了p0的状态为fufilled(成功)还是rejected(失败)。 2. 继续往下执行`p1 = p0.then()`、`p3 = p1.then()`、`p4 = p3.then()`、`p4.catch()`,`p0.then()`、`p1.then()`、`p3.then()`、`p4.catch()`等依次推入微任务队列,p1、p3、p4的状态变为pending(初始状态)。此处p4添加了`catch()`方法,若p4也有`then()`方法,那么推入队列的就是`p4.then().catch()`。 3. 执行`console.log(2)`。宏任务执行完毕。 4. 从微任务队列中取出`p0.then()`。如果p0的状态为fufilled,那么执行`p0.then()`:打印`succeed-1`,然后执行`new Promise(executor)`,完毕后p1的状态转为fufilled/rejected;如果p0的状态为rejected,则不执行`p0.then()`,p1的状态置为rejected。 5. 继续从微任务队列取出`p1.then()`、`p3.then()`,他们的处理方式与第3步同理。 6. 取出`p4.catch()`,如果p4的状态为rejected,那么执行`p4.catch()`,否则啥也不做。结束。2019-09-14411
- splm前面的课程举过说过,正常任务会被放在消息队列中,延时任务会放在延时消息队列中,还举过一段代码,任务循环会不断的从消息队列中取任务,并执行,也会不断的判断延时任务是否到期需要执行。但在这节课里面却说延迟任务会追加到消息队列末尾,听说去就像普通任务和延迟任务都在一起,只是延迟任务被追加到末尾。究竟有几个消息队列,普通和延迟队列是真实存在还是只是概念区分,实际两种任务都保存在一块。2019-10-1658
- ytd执行过程: 从第10行开始: 1,创建promise赋值,打印1 rand 2,执行log语句,打印2 3,如果rand > 0.5,promise被resolve,打印success,并返回新的promse赋值 然后重复类似步骤1、3、4 4,否则如果rand <= 0.5 promise reject,然后p1、p2、p3、p4都分别被赋值为一个新的被reject的promise,最后在p4.catch中reject状态被捕获,打印error2019-09-1427