浏览器工作原理与实践
李兵
前盛大创新院高级研究员
立即订阅
6167 人已学习
课程目录
已完结 42 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 参透了浏览器的工作原理,你就能解决80%的前端难题
免费
宏观视角下的浏览器 (6讲)
01 | Chrome架构:仅仅打开了1个页面,为什么有4个进程?
02 | TCP协议:如何保证页面文件能被完整送达浏览器?
03 | HTTP请求流程:为什么很多站点第二次打开速度会很快?
04 | 导航流程:从输入URL到页面展示,这中间发生了什么?
05 | 渲染流程(上):HTML、CSS和JavaScript,是如何变成页面的?
06 | 渲染流程(下):HTML、CSS和JavaScript,是如何变成页面的?
浏览器中的JavaScript执行机制 (5讲)
07 | 变量提升:JavaScript代码是按顺序执行的吗?
08 | 调用栈:为什么JavaScript代码会出现栈溢出?
09 | 块级作用域:var缺陷以及为什么要引入let和const?
10 | 作用域链和闭包 :代码中出现相同的变量,JavaScript引擎是如何选择的?
11 | this:从JavaScript执行上下文的视角讲清楚this
V8工作原理 (3讲)
12 | 栈空间和堆空间:数据是如何存储的?
13 | 垃圾回收:垃圾数据是如何自动回收的?
14 | 编译器和解释器:V8是如何执行一段JavaScript代码的?
浏览器中的页面循环系统 (6讲)
15 | 消息队列和事件循环:页面是怎么“活”起来的?
16 | WebAPI:setTimeout是如何实现的?
17 | WebAPI:XMLHttpRequest是怎么实现的?
18 | 宏任务和微任务:不是所有任务都是一个待遇
19 | Promise:使用Promise,告别回调函数
20 | async/await:使用同步的方式去写异步代码
浏览器中的页面 (8讲)
21 | Chrome开发者工具:利用网络面板做性能分析
22 | DOM树:JavaScript是如何影响DOM树构建的?
23 | 渲染流水线:CSS如何影响首次加载时的白屏时间?
24 | 分层和合成机制:为什么CSS动画比JavaScript高效?
25 | 页面性能:如何系统地优化页面?
26 | 虚拟DOM:虚拟DOM和实际的DOM有何不同?
27 | 渐进式网页应用(PWA):它究竟解决了Web应用的哪些问题?
28 | WebComponent:像搭积木一样构建Web应用
浏览器中的网络 (3讲)
29 | HTTP/1:HTTP性能优化
30|HTTP/2:如何提升网络速度?
31|HTTP/3:甩掉TCP、TLS 的包袱,构建高效网络
浏览器安全 (5讲)
32 | 同源策略:为什么XMLHttpRequest不能跨域请求资源?
33 | 跨站脚本攻击(XSS):为什么Cookie中有HttpOnly属性?
34 | CSRF攻击:陌生链接不要随便点
35 | 安全沙箱:页面和系统之间的隔离墙
36 | HTTPS:让数据传输更安全
结束语 (1讲)
结束语 | 大道至简
课外加餐 (4讲)
加餐一|浏览上下文组:如何计算Chrome中渲染进程的个数?
加餐二|任务调度:有了setTimeOut,为什么还要使用rAF?
加餐三|加载阶段性能:使用Audits来优化Web性能
加餐四|页面性能工具:如何使用Performance?
浏览器工作原理与实践
登录|注册

18 | 宏任务和微任务:不是所有任务都是一个待遇

李兵 2019-09-14
在前面几篇文章中,我们介绍了消息队列,并结合消息队列介绍了两种典型的 WebAPI——setTimeoutXMLHttpRequest,通过这两个 WebAPI 我们搞清楚了浏览器的消息循环系统是怎么工作的。不过随着浏览器的应用领域越来越广泛,消息队列中这种粗时间颗粒度的任务已经不能胜任部分领域的需求,所以又出现了一种新的技术——微任务微任务可以在实时性和效率之间做一个有效的权衡
从目前的情况来看,微任务已经被广泛地应用,基于微任务的技术有 MutationObserver、Promise 以及以 Promise 为基础开发出来的很多其他的技术。所以微任务的重要性也与日俱增,了解其底层的工作原理对于你读懂别人的代码,以及写出更高效、更具现代的代码有着决定性的作用。
有微任务,也就有宏任务,那这二者到底有什么区别?它们又是如何相互取长补短的?

宏任务

前面我们已经介绍过了,页面中的大部分任务都是在主线程上执行的,这些任务包括了:
渲染事件(如解析 DOM、计算布局、绘制);
用户交互事件(如鼠标点击、滚动页面、放大缩小等);
JavaScript 脚本执行事件;
网络请求完成、文件读写完成事件。
为了协调这些任务有条不紊地在主线程上执行,页面进程引入了消息队列和事件循环机制,渲染进程内部会维护多个消息队列,比如延迟执行队列和普通的消息队列。然后主线程采用一个 for 循环,不断地从这些任务队列中取出任务并执行任务。我们把这些消息队列中的任务称为宏任务
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《浏览器工作原理与实践》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(28)

  • 阿桐
    买过不少专栏,每一篇都紧跟并且会反复看的目前只有这一个。一方面懒另一方面是好的系统性的学习资料不多,所以以前很少关注偏底层原理性的东西,所以这个专栏学习起来是既收获满满有时也不乏一额头问号。

    这里有 2 个问题想向老师请教,希望老师百忙之中能抽空解答一下,多谢多谢。

    1、之前讲过,在循环系统的一个循环中,先从消息队列头部取出一个任务执行,该任务执行完后,再去延迟队列中找到所有的过期任务依次执行完。那前面这句话和本篇文章的这句话好像有矛盾:"先从多个消息队列中选出一个最老的任务,这个任务称为 oldestTask"

    2、”通常情况下,在当前宏任务中的 JavaScript 快执行完成时,也就在 JavaScript 引擎准备退出全局执行上下文并清空调用栈的时候,JavaScript 引擎会检查全局执行上下文中的微任务队列,然后按照顺序执行队列中的微任务。“
    在页面生存周期内,全局执行上下文只有一份并且会一直存在调用栈中,只有当页面被关闭的时候全局执行上下文才会消失。页面都快关闭了,把全局执行上下文中的微任务队列中的任务都执行一遍,好像也没啥意义。系统应该不会做没有意义的事情,所以应该是我对全局执行上下文的某处理解有问题,但我又自查不到。

    作者回复:

    非常高兴你能提出这些问题。

    你有这两个疑问很正常,说明你看得很仔细,之所以你会感到疑惑,主要是我在写作过程中偷懒了。

    我先来解答你的第一个问题:
    第一段话是WHATWG标准定义的,在WHATWG规范,定义了在主线程的循环系统中,可以有多个消息队列,比如鼠标事件的队列,IO完成消息队列,渲染任务队列,并且可以给这些消息队列排优先级。

    但是在浏览器实现的过程中,目前只有一个消息队列,和一个延迟执行队列。
    一个是规范,一个是实现,主要我没有在文中强调这点,所以你会产生的这样的疑问。

    关于第二个问题解释起来就比较复杂了,涉及到来了V8是怎么执行的了,专栏中的"全局执行上下文"我没有深入分析。所以我偷懒了,把两个稍微有点不同的概念都称为了“全局执行上下文”,要解释清楚这个问题还要牵涉到V8的一个底层逻辑,既然你提出来了,那我就打算在课程结束后,通过加餐的形式来开一讲,讲清楚了这个还能额外地理解 Realm 概念。

    2019-09-17
    3
    27
  • 安追
    错误和缺失之处烦请老师指正:
    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-14
    1
    4
  • 一步
    老师,我看文章的图展示,微任务队列只存在全局执行上下文中吗? 如果一个微任务是在一个函数执行上下文中产生了,也会保存到全局执行上下文中的微队列中吗?
    2019-09-15
    1
    3
  • 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状态被捕获,打印error
    2019-09-14
    3
  • 瞧,这个人
    提问: Mutation Event的回调 是同步的吗?如果是同步的,引擎是怎么做到的?
    同步代码执行的时候,还能插入其他代码(mutation 回调)?

    作者回复: 要理解这个就得讲观察者模式了,不过展开又是一篇文章,我到时候加餐的时候再来结合观察者模式来讲这个。

    2019-09-17
    1
    2
  • locke
    文中“第一种是把异步回调函数封装成一个宏任务,添加到消息队列尾部”,setTimeout--不是添加到消息队列尾部吧,不应该延迟队列吗?
    2019-10-17
    1
    1
  • splm
    延迟任务队列是真实存在的还是只是一个定义,实际上就是追加普通宏任务队列后面或者被添加到微任务中的任务集?
    2019-10-16
    1
  • splm
    前面的课程举过说过,正常任务会被放在消息队列中,延时任务会放在延时消息队列中,还举过一段代码,任务循环会不断的从消息队列中取任务,并执行,也会不断的判断延时任务是否到期需要执行。但在这节课里面却说延迟任务会追加到消息队列末尾,听说去就像普通任务和延迟任务都在一起,只是延迟任务被追加到末尾。究竟有几个消息队列,普通和延迟队列是真实存在还是只是概念区分,实际两种任务都保存在一块。
    2019-10-16
    1
  • Andy Jiang
    之前讲过,在循环系统的一个循环中,先从消息队列头部取出一个任务执行,该任务执行完后,再去延迟队列中找到所有的过期任务依次执行完。消息队列头部取出的任务执行完毕后,会先检查微任务队列么?检查微任务队列,然后再去延迟队列中找过期任务执行?
    2019-10-15
    1
  • 任振鹏
    调用栈:全局执行上下文 -> executor函数执行上下文
    先执行宏任务队列:
      先打印1, rand,返回一个promiss,executor函数执行上下文出栈,执行全局上下文的微任务队列加入pomiss在打印2
    然后执行微任务队列:遇到reject 打印error 结束, 否则:打印succeed-1,然后executor函数执行上下文进栈,继续上面步骤。直至reject结束
    不知道上面的分析对不对, 希望老师解答下。
    2019-10-14
    1
  • 小智
    反复读了几次,还是有很多疑问,不同于最初的几篇文章,这里的理论偏多,希望能有更多的案例结合理论分析,才能进一步验证心中理解的

    作者回复: 嗯。后面有疑问正常,理论偏多,而且篇幅之间的依赖性比较强。可以把你的问题列出来,答疑的时候我会结合实际列子来分析。

    2019-09-19
    1
    1
  • liu_xm
    您好,有些疑问。
    1.微任务是在v8引擎中添加的,是不是在ie和firefox中都不存在呢?
    2.再说dom变化多次后再出发muation observer。这个多次是如何定义和谁来控制的呀?
    2019-09-16
    1
    1
  • 海之蓝心
    老师,文中提到,微任务早于宏任务执行?
    我的理解:当前宏任务执行完退出之前,再执行微任务列表的任务。
    还有当前宏任务又有延迟列表任务,又有微任务,应该怎么执行?
    求解惑??
    2019-12-06
  • 凭实力写bug
    执行流程很清楚,也知道了几个队列的关系,看完唯一迷惑的是那里算一个任务,对着performance执行流程就很清晰了

    作者回复: 后续有补充内容

    2019-11-02
  • Cris
    这篇赞
    2019-10-04
  • Cris
    先从多个消息队列中选出一个最老的任务,这个任务称为 oldestTask
    老师一次事件循环只执行多个消息队列里最老的任务(即一次事件循环只执行一个任务)还是取每个消息列表里最老的任务(即一次事件循环会执行多个任务)?
    2019-10-04
  • Cris
    为了协调这些任务有条不紊地在主线程上执行,页面进程引入了消息队列和事件循环机制,渲染进程内部会维护多个消息队列,比如延迟执行队列和普通的消息队列。老师渲染进程维护的消息队列除了延时队列和普通的消息队列是不是还有其他的队列?
    2019-10-04
  • Djan Unchained
    addEventListener 是不是和 Mutation Event 一样也是同步回调?因为它只监听某个DOM节点,所以不会造成太大性能问题?
    2019-09-29
  • 浪里行舟
    "页面中的大部分任务都是在主线程上执行的",请问老师,这里的主线程是在渲染进程中还是浏览器主进程中,这个问题困扰很久,麻烦老师有空解答下
    2019-09-28
    3
  • 蓝配鸡
    迟到交作业:
    思考题的结果大致如下

    1
    rand
    2
    ——————-分割线————————-
    从这里开始主函数结束, 微任务队列里有一个reject或者resolve
    如果是reject:
    输出 error,由于微任务队列已空, 退出当前宏任务
    如果是resolve:
    执行then里的回掉函数
    输出 succeed-1
    并再一次创建一个promise, 当执行executor的时候会再一次往微任务队列里添加任务.

    循环♻️
    2019-09-25
收起评论
28
返回
顶部