专栏首页web前端技术分享浏览器中的 Event Loop,宏任务与微任务

浏览器中的 Event Loop,宏任务与微任务

  当我们执行 JS 代码的时候其实就是往执行栈中放入函数,那么遇到异步代码的时候该怎么办?其实当遇到异步的代码时,会被挂起并在需要执行的时候加入到 Task(有多种 Task) 队列中。一旦执行栈为空,Event Loop 就会从 Task 队列中拿出需要执行的代码并放入执行栈中执行,所以本质上来说 JS 中的异步还是同步行为。

  不同的任务源会被分配到不同的 Task 队列中,任务源可以分为 微任务(microtask) 和 宏任务(macrotask)。在 ES6 规范中,microtask 称为 jobs ,macrotask 称为 task 。下面来看以下代码的执行顺序:

console.log('script start')

async function async1() {
  await async2()
  console.log('async1 end')
}
async function async2() {
  console.log('async2 end')
}
async1()

setTimeout(function() {
  console.log('setTimeout')
}, 0)

new Promise(resolve => {
  console.log('Promise')
  resolve()
})
  .then(function() {
    console.log('promise1')
  })
  .then(function() {
    console.log('promise2')
  })

console.log('script end')
// script start => async2 end => Promise => script end => promise1 => promise2 => async1 end => setTimeout

  注意:新的浏览器中不是如上打印的,因为 await 变快了,具体内容可以往下看

  首先先来解释下上述代码的 async 和 await 的执行顺序。当我们调用 async1 函数时,会马上输出 async2 end,并且函数返回一个 Promise,接下来在遇到 await 的时候会就让出线程开始执行 async1 外的代码,所以我们完全可以把 await 看成是让出线程的标志。

  然后当同步代码全部执行完毕以后,就会去执行所有的异步代码,那么又会回到 await 的位置执行返回的 Promise 的 resolve 函数,这又会把 resolve 丢到微任务队列中,接下来去执行 then 中的回调,当两个 then 中的回调全部执行完毕以后,又会回到 await 的位置处理返回值,这时候你可以看成是 Promise.resolve (返回值).then() ,然后 await 后的代码全部被包裹进了 then 的回调中,所以 console.log(' async1 end ') 会优先执行于 setTimeout。

  如果你觉得上面这段解释还是有点绕,那么我把 async 的这两个函数改造成你一定能理解的代码

new Promise((resolve, reject) => {
  console.log('async2 end')
  // Promise.resolve() 将代码插入微任务队列尾部
  // resolve 再次插入微任务队列尾部
  resolve(Promise.resolve())
}).then(() => {
  console.log('async1 end')
})

也就是说,如果 await 后面跟着 Promise 的话,async1 end 需要等待三个 tick 才能执行到。那么其实这个性能相对来说还是略慢的,所以 V8 团队借鉴了 Node 8 中的一个 Bug,在引擎底层将三次 tick 减少到了二次 tick。但是这种做法其实是违法了规范的,当然规范也是可以更改的,这是 V8 团队的一个 PR,目前已被同意这种做法。

所以 Event Loop 执行顺序如下所示:

  • 首先执行同步代码,这属于宏任务
  • 当执行完所有同步代码后,执行栈为空,查询是否有异步代码需要执行
  • 执行所有微任务
  • 当执行完所有微任务后,如有必要会渲染页面
  • 然后开始下一轮 Event Loop,执行宏任务中的异步代码,也就是 setTimeout 中的回调函数

所以以上代码虽然 setTimeout 写在 Promise 之前,但是因为 Promise 属于微任务而 setTimeout 属于宏任务,所以会有以上的打印。

微任务包括 process.nextTick ,promise ,MutationObserver 。

宏任务包括 script , setTimeout ,setInterval ,setImmediate ,I/O ,UI rendering 。

这里很多人会有个误区,认为微任务快于宏任务,其实是错误的。因为宏任务中包括了 script ,浏览器会先执行一个宏任务,接下来有异步代码的话才会先执行微任务。

转载自:yck的https://juejin.im/book/5bdc715fe51d454e755f75ef/section/5be04a8e6fb9a04a072fd2c

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • JS-字符串截取方法slice、substring、substr的区别

    slice() 方法可通过指定的开始和结束位置,提取字符串的某个部分,并以新的字符串返回被提取的部分。语法如下:

    TimothyJia
  • <script>属性async和defer的区别

    async:可选。表示应该立即下载脚本,但不应妨碍页面中的其他操作,比如下载其他资源或 等待加载其他脚本。只对外部脚本文件有效。

    TimothyJia
  • js中clientWidth, scrollWidth, innerWidth, outerWidth和offsetWidth属性的区别

    一、测试1:无滚动条时,dom对象的offsetWidth、clientWidth和scrollWidth

    TimothyJia
  • Promise的错误处理

    在开发过程中我们经常使用Promise来处理异步,但是我们经常忽略Promise的错误处理。

    挥刀北上
  • Promise: 给我一个承诺,我还你一个承诺

    处理concurrent programming,除了threading/multi-processing外,各家语言都有自己的绝活:erlang/elixir...

    tyrchen
  • 快速开始 - 构建文档 - ckeditor5中文文档

    <textarea name="content" id="editor"></textarea>

    ianzhi
  • 10 个超有用的 JavaScript 技巧

    众所周知,JavaScript 一直在迅速变化。新的 ES2020 引入了许多很棒的功能。你可以通过很多不同的方式去编写代码。他们会达到相同的目标,但其中一些会...

    疯狂的技术宅
  • 周末学会了 10个超级实用 Javascript 技巧!

    众所周知,JavaScript 一直在快速变化。在新的 ES2020 中,有很多很棒的特性,我们大都已经迫不及待尝试了。老实说,有时我们可以用不同角度来编写代码...

    前端小智@大迁世界
  • Jeff Dean两年AMA全盘点:26个关于谷歌大脑和机器学习未来的问题

    AI科技评论按:前两天,谷歌大脑团队又在Reddit上做了一次AMA(Ask me anything)活动。 去年8月时候谷歌大脑团队也在Reddit上做过同样...

    AI科技评论
  • “智能+”助跑未来,机器人正从科幻走进日常!

    3月5日,第十三届全国人民代表大会第二次会议开幕,国务院总理李克强在政府工作报告中首次提出了“智能+”。与此同时,人工智能已经连续三年在政府工作报告中被提及。而...

    AiTechYun

扫码关注云+社区

领取腾讯云代金券