专栏首页小码农学习笔记JavaScript 事件循环机制 - 微任务和宏任务的关系
原创

JavaScript 事件循环机制 - 微任务和宏任务的关系

事件循环机制,微任务和宏任务的关系

本文涉及到的名词:事件循环(Event Loop),宏任务(macro-task)与微任务(micro-task),执行栈和任务队列等。

前言

JavaScript 是单线程的,同一时间只能做一件事情。如果碰到某个耗时长的任务(比如一个需要 3s 的网络请求),那么后续的任务都要等待,这种效果是无法接受的,这时我们就引入了异步任务的概念。

所以 JavaScript 执行主要包括同步任务和异步任务:

同步任务:会放入到执行栈中,他们是要按顺序执行的任务;

异步任务:会放入到任务队列中,这些异步任务一定要等到执行栈清空后才会执行,也就是说异步任务一定是在同步任务之后执行的。

本文所讲的 JavaScript 事件循环机制,它主要与异步任务有关。

任务队列

事件循环主要与任务队列有关,所以必须要先知道宏任务与微任务。

在任务队列中,有两种任务:宏任务和微任务。

宏任务:script 标签中的整体代码、setTimeout、setInterval、setImmediate、I/0、UI渲染

微任务:process.nextTick(Node.js)、Promise、Object.observe(不常用)、MutationObserver(Node.js)

任务优先级:process.nextTick > Promise.then > setTimeout > setImmediate

以上这些是常见的宏任务和微任务,记住就行了,不用追究为什么它是宏任务或微任务,因为就是这样的。

事件循环

那么什么是事件循环机制呢?

  • 一开始整个脚本(script 标签中的整体代码)作为一个宏任务执行;
  • 执行过程中同步代码直接执行,宏任务进入宏任务队列,微任务进入微任务队列;
  • 当前宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行);
  • 当前宏任务执行完毕,开始检查渲染,然后 GUI 线程接管渲染(浏览器会在两个宏任务交接期间,对页面进行重新渲染);
  • 渲染完毕后,JavaScript 线程继续接管,开始下一个宏任务(从任务队列中获取),依此循环,直到宏任务和微任务队列都为空。

上面这一过程就称为:事件循环(Event Loop)。

说的通俗一点:微任务是小跟班,一直跟在当前宏任务后面:代码执行过程中,每当碰到一个微任务,就马上跟在当前宏任务后面;当碰到一个宏任务,那不好意思你排到下一次循环再说。

代码示例

我们执行如下一段代码,用上面的思路执行,看一下结果是否和预期的一致。

console.log('script start')

// 宏任务
setTimeout(() => {
  console.log('setTimeout')
}, 0)

// 微任务 跟在当前宏任务后面
new Promise((resolve) => {
  console.log('new Promise')
  resolve()
  console.log('promise body')
}).then(() => {
  console.log('promise.then 1')
}).then(() => {
  console.log('promise.then 2')
})

console.log('script end')

按照上面的思路,我们来理一下,预测一下执行结果,看看实际效果是否是这样的。

执行流程:

  • 第一次事件循环
    • 首先这一整段 JavaScript 代码作为一个宏任务先被执行
    • 遇到 console.log('script start'),打印出 "script start"
    • 遇到 setTimeout,回调函数作为宏任务压入到宏任务队列中,此时宏任务队列:[setTimeout]
    • 遇到 new Promise,由于 new 一个对象是瞬间执行的,不是异步,所以打印出 "new Promise"
    • 继续执行,由于 Promise 中的异步逻辑在 then 里面,在 then 之前的都不是异步,所以打印出 "promise body"
    • 遇到了第一个 .then,它是个微任务,将它放入微任务队列,跟在当前宏任务(整体代码)后面,此时微任务队列:[promise 1]
    • Promise 的第一个 .then 还没执行,只是排好队伍了,因此继续往后,遇到 console.log('script end'),打印出 "script end"
    • 执行第一个宏任务后的微任务
    • 执行 Promise 的第一个 .then,打印出 "promise 1",,此时微任务队列:[]
    • 又遇到 .then,它是个微任务,将它放入微任务队列,跟在当前宏任务(整体代码)后面,此时微任务队列:[promise 2]
    • 执行 Promise 的第二个 .then,打印出 "promise 2",此时微任务队列:[]
    • 整体代码执行完,微任务队列也执行完,当前的事件循环结束。
  • 第二次事件循环
    • 执行 setTimeout 的回调,打印出 "setTimeout"

预测打印结果:

"script start"
"new Promise"
"promise body"
"script end"
"promise.then 1"
"promise.then 2"
"setTimeout"

执行代码后可以发现,实际打印结果和预测一致。

复杂情况

如果遇到更复杂的场景,比如当前微任务里有微任务,微任务里有宏任务,多层嵌套的情况,只需记住一句话:微任务跟在当前宏任务后面,执行完当前宏任务,微任务就跟上,然后再执行下一个宏任务

应用场景

除了在前端面试中,会问到关于事件循环、执行栈的问题,了解 JavaScript 事件循环机制有没有实质的作用呢?

  • 以后我们在代码中使用 Promise,setTimeout 时,思路将更加清晰,用起来更佳得心应手;
  • 在阅读一些源码时,对于一些 setTimeout 相关的骚操作可以理解的更加深入;
  • 理解 JavaScript 中的任务执行流程,加深对异步流程的理解,少犯错误。

总结

  • JavaScript 事件循环总是从一个宏任务开始执行;
  • 一个事件循环过程中,只执行一个宏任务,但是可能执行多个微任务;
  • 执行栈中的任务产生的微任务会在当前事件循环内执行;
  • 执行栈中的任务产生的宏任务要在下一次事件循环才会执行。

最后的最后,记住,JavaScript 是一门单线程语言,异步操作都是放到事件循环队列里面,等待主执行栈来执行的,并没有专门的异步执行线程。


文章持续更新,本文 GitHub 前端修炼小册 已经收录,欢迎 Star。如对文章内容有不同见解,欢迎留言交流。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 事件循环机制,微任务和宏任务的关系

    JavaScript(后面简称 JS)是单线程的,同一时间只能做一件事情。如果碰到某个耗时长的任务(比如一个需要 3s 的网络请求),那么后续的任务都要等待,这...

    文渊同学
  • 「JS-Learning」事件循环机制,微任务和宏任务的关系

    JavaScript(后面简称 JS)是单线程的,同一时间只能做一件事情。如果碰到某个耗时长的任务(比如一个需要 3s 的网络请求),那么后续的任务都要等待,这...

    文渊同学
  • JS 事件循环、微任务和宏任务

    JS 是单线程执行的,所有 JS 代码都要放在主线程中运行。 如果把异步 IO 等耗时较长的任务也放在主线程中处理,会阻塞后续同步代码的执行,造成卡顿等现象。因...

    lonelydawn
  • JS事件循环之宏任务和微任务

    众所周知,JS 是一门单线程语言,可是浏览器又能很好的处理异步请求,那么到底是为什么呢?

    九旬
  • 这一次,彻底弄懂 JavaScript 执行机制

    本文的目的就是要保证你彻底弄懂javascript的执行机制,如果读完本文还不懂,可以揍我。

    前端迷
  • 深入理解JavaScript的Event-Loop机制

    JavaScript 是单线程的,只有JS引擎线程执行事件队列的事件。为了防止代码阻塞,JavaScript使用了异步执行机制。

    伯爵
  • 【前端进阶】深入浅出浏览器事件循环【内附练习题】

    我们看一个很经典的图,这张图基本可以概括了事件循环(该图来自演讲—— 菲利普·罗伯茨:到底什么是Event Loop呢?| 欧洲 JSConf 2014[1])...

    GopalFeng
  • 新生代总结 JavaScript 运行机制解析

    这些虽然看起来很深奥很复杂,但是如果你了解了 JavaScript 的运行机制,这些问题都能够一一化解

    小丞同学
  • 从Vue.nextTick探究事件循环中的线程协作机制

    ? 一、背景 对vue里的nextTick()方法理解不清晰,会导致api代码滥用的现象,我查看了vue官网的说明: Vue.nextTick()用于在下次 ...

    腾小云
  • 宏任务和微任务的一个小事

    本文根据 JavaScript 规范入手,阐述了JS执行过程在考虑时效性和效率权衡中的演变,并通过从JS代码运行的基础机制事件队列入手,分析了JS不同任务类型(...

    2020labs小助手
  • JavaScript事件循环机制解析

    最近面试了很多家公司,这道题几乎是必被问到的一道题。之前总觉得自己了解得差不多,但是当第一次被问到的时候,却不知道该从哪里开始说起,涉及到的知识点很多。于是花时...

    前端迷
  • 高频面试题:JavaScript事件循环机制解析

    最近面试了很多家公司,这道题几乎是必被问到的一道题。之前总觉得自己了解得差不多,但是当第一次被问到的时候,却不知道该从哪里开始说起,涉及到的知识点很多。于是花时...

    木子星兮
  • 教你做一些动图,学习一下 EventLoop

    https://juejin.cn/post/6969028296893792286

    程序IT圈
  • 你不知道的 Event Loop

    笔者最近忙着做项目之类的,文章输出遗落下了一段时间,这次我们就来聊一个面试中一个比较重要的知识点 —— Event Loop

    一只图雀
  • 浏览器原理学习笔记04—浏览器中的页面事件循环系统

    每个渲染进程都有一个非常繁忙的主线程,需要一个系统来统筹调度任务(具体任务后面详解)

    CS逍遥剑仙
  • 全方位理解JavaScript的Event Loop

    下面我们一个一个的来了解 Event Loop 相关的知识点,最后再一步一步分析出本段代码最后的输出顺序。

    laixiangran
  • JavaScript之Event Loop

    先看段代码: console.log(1); setTimeout(function () { console.log(2); new Pr...

    laixiangran
  • JavaScript Event Loop

    JavaScript 有一个基于事件循环的并发模型,事件循环负责执行代码、收集和处理事件以及执行队列中的子任务。事件循环包含一个函数执行栈、一个宏任务队列、一个...

    多云转晴
  • 小白理解 JavaScript 执行机制

    所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。

    前端魔法师

扫码关注云+社区

领取腾讯云代金券