18 | 宏任务和微任务:不是所有任务都是一个待遇
18 | 宏任务和微任务:不是所有任务都是一个待遇
讲述:李兵
时长13:41大小12.51M
宏任务
微任务
监听 DOM 变化方法演变
总结
思考时间
赞 36
提建议
精选留言(76)
- 阿桐2019-09-17买过不少专栏,每一篇都紧跟并且会反复看的目前只有这一个。一方面懒另一方面是好的系统性的学习资料不多,所以以前很少关注偏底层原理性的东西,所以这个专栏学习起来是既收获满满有时也不乏一额头问号。 这里有 2 个问题想向老师请教,希望老师百忙之中能抽空解答一下,多谢多谢。 1、之前讲过,在循环系统的一个循环中,先从消息队列头部取出一个任务执行,该任务执行完后,再去延迟队列中找到所有的过期任务依次执行完。那前面这句话和本篇文章的这句话好像有矛盾:"先从多个消息队列中选出一个最老的任务,这个任务称为 oldestTask" 2、”通常情况下,在当前宏任务中的 JavaScript 快执行完成时,也就在 JavaScript 引擎准备退出全局执行上下文并清空调用栈的时候,JavaScript 引擎会检查全局执行上下文中的微任务队列,然后按照顺序执行队列中的微任务。“ 在页面生存周期内,全局执行上下文只有一份并且会一直存在调用栈中,只有当页面被关闭的时候全局执行上下文才会消失。页面都快关闭了,把全局执行上下文中的微任务队列中的任务都执行一遍,好像也没啥意义。系统应该不会做没有意义的事情,所以应该是我对全局执行上下文的某处理解有问题,但我又自查不到。展开
作者回复: 非常高兴你能提出这些问题。 你有这两个疑问很正常,说明你看得很仔细,之所以你会感到疑惑,主要是我在写作过程中偷懒了。 我先来解答你的第一个问题: 第一段话是WHATWG标准定义的,在WHATWG规范,定义了在主线程的循环系统中,可以有多个消息队列,比如鼠标事件的队列,IO完成消息队列,渲染任务队列,并且可以给这些消息队列排优先级。 但是在浏览器实现的过程中,目前只有一个消息队列,和一个延迟执行队列。 一个是规范,一个是实现,主要我没有在文中强调这点,所以你会产生的这样的疑问。 关于第二个问题解释起来就比较复杂了,涉及到来了V8是怎么执行的了,专栏中的"全局执行上下文"我没有深入分析。所以我偷懒了,把两个稍微有点不同的概念都称为了“全局执行上下文”,要解释清楚这个问题还要牵涉到V8的一个底层逻辑,既然你提出来了,那我就打算在课程结束后,通过加餐的形式来开一讲,讲清楚了这个还能额外地理解 Realm 概念。
共 10 条评论130 - man-moonth2019-09-14错误和缺失之处烦请老师指正: 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()`,否则啥也不做。结束。展开共 3 条评论10
- splm2019-10-16前面的课程举过说过,正常任务会被放在消息队列中,延时任务会放在延时消息队列中,还举过一段代码,任务循环会不断的从消息队列中取任务,并执行,也会不断的判断延时任务是否到期需要执行。但在这节课里面却说延迟任务会追加到消息队列末尾,听说去就像普通任务和延迟任务都在一起,只是延迟任务被追加到末尾。究竟有几个消息队列,普通和延迟队列是真实存在还是只是概念区分,实际两种任务都保存在一块。共 5 条评论8
- 花儿与少年2019-09-17提问: Mutation Event的回调 是同步的吗?如果是同步的,引擎是怎么做到的? 同步代码执行的时候,还能插入其他代码(mutation 回调)?
作者回复: 要理解这个就得讲观察者模式了,不过展开又是一篇文章,我到时候加餐的时候再来结合观察者模式来讲这个。
共 2 条评论8 - 一步2019-09-15老师,我看文章的图展示,微任务队列只存在全局执行上下文中吗? 如果一个微任务是在一个函数执行上下文中产生了,也会保存到全局执行上下文中的微队列中吗?共 2 条评论6
- Jankin2020-07-27。。。老师,记录学习的话,写博客可以引用您的话之类的吗。。会注明作者和链接的5
- ytd2019-09-14执行过程: 从第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状态被捕获,打印error展开共 1 条评论5
- Matt2020-05-31我是这样理解的,setTimeout代码被执行后,延迟队列中会添加一个延迟任务。当一个宏任务被执行完后,渲染主线程会去检查延迟队列中是否有到达触发时间的延迟任务,如果有,则将其从延迟任务队列中清除,并在宏任务队列中添加一个待执行的任务(setTimeout的回调函数)。所以两个setTimout任务中间才有可能插入其他的系统任务。共 1 条评论4
- HoSalt2020-04-10老师,通过控制面板中修改的样式是不是不会触发MutationObserver?
作者回复: 不会,因为没有出发js,这个微任务是v8触发的
共 2 条评论3 - coder2019-12-22对于文中一处有疑虑: “第一种是把异步回调函数封装成一个宏任务,添加到消息队列尾部,当循环系统执行到该任务的时候执行回调函数。这种比较好理解,我们前面介绍的 setTimeout 和 XMLHttpRequest 的回调函数都是通过这种方式来实现的。” 第16讲提到了,setTimeout里的延迟任务,是存在一个延迟队列中的。我看精选留言部分老师的回答,提到这个延迟队列实际上是一个hashmap,那么setTimeout的实现还是加到队尾,等到前面的都出队,才执行到这个任务的吗?展开
作者回复: setTimeout因为是定时任务,设定的时间间隔没有到是不会执行,由此需要一个单独的模块来保存定时器的消息,你可以通过该模块取出到期的任务,我们把这个模块叫延时队列,Chrome内部用了个hashmap保存数据,然后又写了取出到期的任务的策略! 通常情况下,是当渲染主线程在执行完一个正常的任务之后,再判断该模块中是否有到期的任务,如果有取出来执行!
共 3 条评论3 - locke2019-10-17文中“第一种是把异步回调函数封装成一个宏任务,添加到消息队列尾部”,setTimeout--不是添加到消息队列尾部吧,不应该延迟队列吗?共 1 条评论2
- Andy Jiang2019-10-15之前讲过,在循环系统的一个循环中,先从消息队列头部取出一个任务执行,该任务执行完后,再去延迟队列中找到所有的过期任务依次执行完。消息队列头部取出的任务执行完毕后,会先检查微任务队列么?检查微任务队列,然后再去延迟队列中找过期任务执行?共 1 条评论2
- sundy2022-02-02JavaScript 脚本执行事件;请问指的是什么1
- james2020-06-091. 执行上下文栈(调用栈): 全局执行上下文+函数执行上下文(executor) 2. new Promise中接收的参数是一个立即执行函数,属于同步任务,因此会立即执行,打印出1和随机数,如果随机数>0.5,则让这个Promise变为成功态(fulfilled), 否则变为失败态(rejected),执行完成后,再依次执行下面的代码,js引擎扫描到p1、p3、p4以及p4.catch四个方法,这些都是基于前一个Promise做的状态来执行的,会产生微任务,因此会被推入到当前宏任务的微任务队列中,最后面是一句同步代码:console.log(2),会立即执行打印2,到这里,当前宏任务的所有同步代码全部执行完,JavaScript 引擎就会检查微任务列表,发现微任务列表中有微任务(4个Promise微任务待执行),那么接下来,依次执行这四个微任务。等微任务队列清空之后,就退出全局执行上下文。展开1
- 王妍2020-05-18首先执行同步代码, promise的构造函数是同步执行的,所以先执行executor。 打印1, 打印rand, 之后看生成的随机数是不是大于0.5。 这里假设每一次都大于0.5. 因为大于0.5,将p0的then回调加入到微任务队列中。 然后同步执行到console.log(2), 打印2 这是宏任务执行结束,开始执行微任务队列的内容。 打印“succeed-1” 然后同步执行executor。 打印1 打印rand 然后因为大于0.5,将p1的then回调加入到微任务队列中。 这是一个微任务执行完成,接下来执行刚刚加入的微任务。 打印"succeed-2" 然后同步执行executor 打印1 打印rand 。。。 如果过程中某一步产生的随机数小于0.5,则将error回调加入微任务,打印error。中止。展开1
- 夏了夏天2019-12-18老师,我有个疑惑,主线程读取消息队列里的任务的时机是「系统调用栈」的任务执行完毕时还是「JS的调用栈」执行完毕时?
作者回复: js只不是任务的一个过程,这里讲的都是C++层面的,所以也可以说是系统调用栈。 具体的你可以看看加餐5
1 - splm2019-10-16延迟任务队列是真实存在的还是只是一个定义,实际上就是追加普通宏任务队列后面或者被添加到微任务中的任务集?共 1 条评论1
- 任振鹏2019-10-14调用栈:全局执行上下文 -> executor函数执行上下文 先执行宏任务队列: 先打印1, rand,返回一个promiss,executor函数执行上下文出栈,执行全局上下文的微任务队列加入pomiss在打印2 然后执行微任务队列:遇到reject 打印error 结束, 否则:打印succeed-1,然后executor函数执行上下文进栈,继续上面步骤。直至reject结束 不知道上面的分析对不对, 希望老师解答下。展开1
- 蓝配鸡2019-09-25迟到交作业: 思考题的结果大致如下 1 rand 2 ——————-分割线————————- 从这里开始主函数结束, 微任务队列里有一个reject或者resolve 如果是reject: 输出 error,由于微任务队列已空, 退出当前宏任务 如果是resolve: 执行then里的回掉函数 输出 succeed-1 并再一次创建一个promise, 当执行executor的时候会再一次往微任务队列里添加任务. 循环♻️展开1
- 小智2019-09-19反复读了几次,还是有很多疑问,不同于最初的几篇文章,这里的理论偏多,希望能有更多的案例结合理论分析,才能进一步验证心中理解的
作者回复: 嗯。后面有疑问正常,理论偏多,而且篇幅之间的依赖性比较强。可以把你的问题列出来,答疑的时候我会结合实际列子来分析。
共 2 条评论1