JS事件循环机制(event loop)之宏任务/微任务

2023-10-30

原文地址:Tasks, microtasks, queues and schedules

视频讲解地址:https://www.bilibili.com/video/av83625811?from=search&seid=6644038259868014366

鉴于上篇文章有提到过 微任务与宏任务,所以,在此做个细致补充: 话不多说,直接进入正文:

[1] 本文主要根据网上资源总结而来,如有不对,请斧正。 [2] 需要知道的专业名词术语:synchronous:同步任务、asynchronous:异步任务、task queue/callback queue:任务队列、execution context stack:执行栈、heap:堆、stack:栈、macro-task:宏任务、micro-task:微任务


首先我们要知道两点:

  • JavaScript是单线程的语言
  • Event Loop是javascript的执行机制

javascript事件循环

js是单线程,就像学生排队上厕所,学生需要排队一个一个上厕所,同理js任务也要一个一个顺序执行。如果一个任务耗时过长,那么后一个任务也必须等着。那么问题来了,假如我们想浏览新闻,但是新闻包含的超清图片加载很慢,难道我们的网页要一直卡着直到图片完全显示出来?因此聪明的程序员将任务分为两类:

  • 同步任务
  • 异步任务

 

cmd-markdown-logo

 

 

从图片可知,一个方法执行会向执行栈中加入这个方法的执行环境,在这个执行环境中还可以调用其他方法,甚至是自己,其结果不过是在执行栈中再添加一个执行环境。这个过程可以是无限进行下去的,除非发生了栈溢出,即超过了所能使用内存的最大值。


当我们打开网站时,网页的渲染过程就是一大堆同步任务,比如页面骨架和页面元素的渲染。而像加载图片音乐之类占用资源大耗时久的任务,就是异步任务。关于这部分有严格的文字定义,但本文的目的是用最小的学习成本彻底弄懂执行机制

先看一段代码:

console.log('script start');

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

Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});

console.log('script end');
复制代码

打印顺序是什么? 正确答案是:script start, script end, promise1, promise2, setTimeout 已蒙圈。。。

为什么会出现这样打印顺序呢?

  • 如下导图(此图从网站下载)

 

cmd-markdown-logo

 

 

解读:

  • 同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数
  • 当指定的事情完成时,Event Table会将这个函数移入Event Queue。
  • 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
  • 上述过程会不断重复,也就是常说的Event Loop(事件循环)。

我们不禁要问了,那怎么知道主线程执行栈为空呢?js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。

看代码:

let data = [];
$.ajax({
    url:www.javascript.com,
    data:data,
    success:() => {
        console.log('发送成功!');
    }
})
console.log('代码执行结束');
复制代码

上面是一段简易的ajax请求代码:

  • ajax进入Event Table,注册回调函数success。
  • 执行console.log('代码执行结束')。
  • ajax事件完成,回调函数success进入Event Queue。
  • 主线程从Event Queue读取回调函数success并执行。

相信通过上面的文字和代码,你已经对js的执行顺序有了初步了解。


微任务(Microtasks)、宏任务(task)?

微任务和宏任务皆为异步任务,它们都属于一个队列,主要区别在于他们的执行顺序,Event Loop的走向和取值。那么他们之间到底有什么区别呢?

 

cmd-markdown-logo

 

 

一个掘金的老哥(ssssyoki)的文章摘要: 那么如此看来我给的答案还是对的。但是js异步有一个机制,就是遇到宏任务,先执行宏任务,将宏任务放入eventqueue,然后在执行微任务,将微任务放入eventqueue最骚的是,这两个queue不是一个queue。当你往外拿的时候先从微任务里拿这个回掉函数,然后再从宏任务的queue上拿宏任务的回掉函数。 我当时看到这我就服了还有这种骚操作。

  • 而宏任务一般是:包括整体代码script,setTimeout,setInterval、setImmediate。
  • 微任务:原生Promise(有些实现的promise将then方法放到了宏任务中)、process.nextTick、Object.observe(已废弃)、 MutationObserver 记住就行了。
  • process是什么?

不废话,看以下例子:

setTimeout

大名鼎鼎的setTimeout无需再多言,大家对他的第一印象就是异步可以延时执行,我们经常这么实现延时3秒执行:

setTimeout(() => {
    console.log('延时3秒');
},3000)
复制代码

渐渐的setTimeout用的地方多了,问题也出现了,有时候明明写的延时3秒,实际却5,6秒才执行函数,这又咋回事啊?

setTimeout(() => {
    task();
},3000)
console.log('执行console');
复制代码

根据前面我们的结论,setTimeout是异步的,应该先执行console.log这个同步任务,所以我们的结论是:

// 执行console
// task()
复制代码

去验证一下,结果正确! 然后我们修改一下前面的代码:

setTimeout(() => {
    task()
},3000)

sleep(10000000)
复制代码

乍一看其实差不多嘛,但我们把这段代码在chrome执行一下,却发现控制台执行task()需要的时间远远超过3秒,说好的延时三秒,为啥现在需要这么长时间啊? 这时候我们需要重新理解setTimeout的定义。我们先说上述代码是怎么执行的:

  • task()进入Event Table并注册,计时开始。
  • 执行sleep函数,很慢,非常慢,计时仍在继续。
  • 3秒到了,计时事件timeout完成,task()进入Event Queue,但是sleep也太慢了吧,还没执行完,只好等着。
  • sleep终于执行完了,task()终于从Event Queue进入了主线程执行。

上述的流程走完,我们知道setTimeout这个函数,是经过指定时间后,把要执行的任务(本例中为task())加入到Event Queue中,又因为是单线程任务要一个一个执行,如果前面的任务需要的时间太久,那么只能等着,导致真正的延迟时间远远大于3秒。


我们还经常遇到setTimeout(fn,0)这样的代码,0秒后执行又是什么意思呢?是不是可以立即执行呢? 答案是不会的,setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,意思就是不用再等多少秒了,只要主线程执行栈内的同步任务全部执行完成,栈为空就马上执行。举例说明:

//代码1
console.log('先执行这里');
setTimeout(() => {
    console.log('执行啦')
},0);

//代码2
console.log('先执行这里');
setTimeout(() => {
    console.log('执行啦')
},3000);

复制代码

代码1的输出结果是:

先执行这里
执行啦
复制代码

代码2的输出结果是:

//先执行这里
// ... 3s later
// 执行啦
复制代码

关于setTimeout要补充的是,即便主线程为空,0毫秒实际上也是达不到的。根据HTML的标准,最低是4毫秒。有兴趣的同学可以自行了解。

setInterval

上面说完了setTimeout,当然不能错过它的孪生兄弟setInterval。他俩差不多,只不过后者是循环的执行。对于执行顺序来说,setInterval会每隔指定的时间将注册的函数置入Event Queue,如果前面的任务耗时太久,那么同样需要等待。

唯一需要注意的一点是,对于setInterval(fn,ms)来说,我们已经知道不是每过ms秒会执行一次fn,而是每过ms秒,会有fn进入Event Queue。一旦setInterval的回调函数fn执行时间超过了延迟时间ms,那么就完全看不出来有时间间隔了。这句话请读者仔细品味。

Promise与process.nextTick(callback)

  • Promise的定义和功能本文不再赘述,可以学习一下 阮一峰老师的Promise
  • 而process.nextTick(callback)类似node.js版的"setTimeout",在事件循环的下一次循环中调用 callback 回调函数。

不同类型的任务会进入对应的Event Queue,比如setTimeoutsetInterval会进入相同的Event Queue。

看例子:

setTimeout(()=>{
  console.log('setTimeout1')
},0)
let p = new Promise((resolve,reject)=>{
  console.log('Promise1')
  resolve()
})
p.then(()=>{
  console.log('Promise2')    
})
复制代码

最后输出结果是Promise1,Promise2,setTimeout1

Promise参数中的Promise1是同步执行的 其次是因为Promise是microtasks,会在同步任务执行完后会去清空microtasks queues, 最后清空完微任务再去宏任务队列取值。

Promise.resolve().then(()=>{
  console.log('Promise1')  
  setTimeout(()=>{
    console.log('setTimeout2')
  },0)
})

setTimeout(()=>{
  console.log('setTimeout1')
  Promise.resolve().then(()=>{
    console.log('Promise2')    
  })
},0)
复制代码

这回是嵌套,大家可以看看,最后输出结果是Promise1,setTimeout1,Promise2,setTimeout2

  • 一开始执行栈的同步任务执行完毕,会去 microtasks queues 找 清空 microtasks queues ,输出Promise1,同时会生成一个异步任务 setTimeout1
  • 去宏任务队列查看此时队列是 setTimeout1 在 setTimeout2 之前,因为setTimeout1执行栈一开始的时候就开始异步执行,所以输出 setTimeout1
  • 在执行setTimeout1时会生成Promise2的一个 microtasks ,放入 microtasks queues 中,接着又是一个循环,去清空 microtasks queues ,输出 Promise2
  • 清空完 microtasks queues ,就又会去宏任务队列取一个,这回取的是 setTimeout2

如下图:

 

 

 

最后我们来分析一段较复杂的代码,看看你是否真的掌握了js的执行机制:

console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})
复制代码

第一轮事件循环流程分析如下:

  • 整体script作为第一个宏任务进入主线程,遇到console.log,输出1
  • 遇到setTimeout,其回调函数被分发到宏任务Event Queue中。我们暂且记为setTimeout1
  • 遇到process.nextTick(),其回调函数被分发到微任务Event Queue中。我们记为process1
  • 遇到Promise,new Promise直接执行,输出7。then被分发到微任务Event Queue中。我们记为then1
  • 又遇到了setTimeout,其回调函数被分发到宏任务Event Queue中,我们记为setTimeout2
宏任务Event Queue 微任务Event Queue
setTimeout1 process1
setTimeout2 then1
  • 上表是第一轮事件循环宏任务结束时各Event Queue的情况,此时已经输出了17

我们发现了process1和then1两个微任务。

  • 执行process1,输出6
  • 执行then1,输出8

好了,第一轮事件循环正式结束,这一轮的结果是输出1,7,6,8。那么第二轮时间循环从setTimeout1宏任务开始:

  • 首先输出2。接下来遇到了process.nextTick(),同样将其分发到微任务Event Queue中,记为process2。
  • new Promise立即执行输出4,then也分发到微任务Event Queue中,记为then2
宏任务Event Queue 微任务Event Queue
setTimeout2 process3
  then3
  • 第三轮事件循环宏任务执行结束,执行两个微任务process3和then3。

  • 输出10

  • 输出12

  • 第三轮事件循环结束,第三轮输出9,11,10,12

  • 整段代码,共进行了三次事件循环,完整的输出为1,7,6,8,2,4,3,5,9,11,10,12。(请注意,node环境下的事件监听依赖libuv与前端环境不完全相同,输出顺序可能会有误差)

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JS事件循环机制(event loop)之宏任务/微任务 的相关文章

  • Javascript树遍历算法

    我需要帮助以深度优先的方式遍历树结构 我无法想出一个算法来正确地做到这一点 我的输入是这样的 A B C 1 2 a b c d 输出应采用以下形式 A 1 a A 1 b A 1 c A 1 d A 2 a A 2 b A 2 c A 2
  • 在javascript中使用“return !0”有什么意义吗?

    如果您转到谷歌结果页面并运行rwt toString 你会看到这个函数的返回调用是 return 0 我想不出任何理由为什么这不会总是如此true 这只是一个简写吗true 还是还有更多事情发生 总是如此 但下载需要 2 个字节 0是 2
  • HTML5 服务器端事件:EventSource 与包装的 WebSocket

    HTML5 服务器发送事件 SSE API 是否只是 HTML5 WebSocket 之上的受限制的 基于事件的 API 在我看来 一个EventSource只是一个WebSocket that Cannot send data 使用tex
  • 在 asp.net vb 中通过第一个下拉列表值填充第二个下拉列表

    我在使用 asp net vb 时遇到了一些问题 我想做的是有2个下拉框 第一个下拉菜单将有 1 2 3 例如 第二个下拉菜单将有 A 乙 C 默认情况下 但是 如果选择 1 我希望第二个下拉菜单自动选择 c 我不知道 JavaScript
  • Chrome SuppressDifferentOriginSubframeJSDialogs 使用 JS 设置覆盖?

    Chrome 开发团队显然刚刚推出了一项名为 SuppressDifferentOriginSubframeJSDialogs 的新 功能 这使得默认情况下不会显示来自与父级不同域上的 iFrame 的警报 确认框 https www ch
  • 是否可以覆盖 javaScript 原始数据类型?

    问题是不言自明的 我知道可以扩展原始数据类型 例如string但有可能覆盖它吗 这是在采访中被问到的一个问题 不 你不能覆盖任何东西 Ecma脚本定义原始类型 http es5 github com x4 3 2 Undefined Nul
  • Telegram 授权无默认按钮

    使用 Telegram 第 3 方授权的唯一有记录的方法是使用其提供的脚本https core telegram org widgets login https core telegram org widgets login 这个脚本 正如
  • 引入 V8 后,Google Apps 脚本无法为其他用户完全执行

    我编写了一个脚本 得到了这里好心人的大力帮助 该脚本使用 Google Sheets 脚本复制 Google Drive 上的文件夹 和内容 它运行了很长一段时间 但后来我启用了 V8 引擎 现在已禁用 问题是 它仍然适用于我 也许还有其他
  • 使用 jQuery 仅从字符串末尾修剪空格

    我知道 jQuery trim 函数 但我需要的是一种仅从字符串末尾修剪空格的方法 而不是开头 So str this is a string 会成为 str this is a string 有什么建议么 Thanks 您可以使用正则表达
  • JQuery DataTable 单元格从行单击

    我正在尝试在 jquery 数据表上实现一个函数 该函数返回单击行的第一列和第四列 我正在遵循这个示例 它允许我操作单击的行http datatables net examples api select single row html ht
  • 点击问题:动态生成的链接不触发点击功能

    下面是两个代码片段 由于某种原因什么也没有发生 但来自同一个 JS 文件的其他 jQuery 函数在带有 UL 的页面上执行得很好 这是在盯着我看吗 ul class paganation li 1 li li a href 2 a li
  • 空 URL 哈希导致页面在 js 事件上跳转

    我有一个带有下一个和上一个按钮的照片库 如果我的某个 javascript 方法由于某种原因被破坏 那么当单击其中一个按钮时 它会向 url 添加一个哈希值 即 www google com 我知道可以给散列一个 div id 来跳转到页面
  • setInterval 内的返回值

    我想在 setInterval 内返回一个值 我只想以一定的时间间隔执行一些操作 这就是我尝试过的 function git limit var i 0 var git setInterval function console log i
  • Web SQL 数据库 + Javascript 循环

    我正在尝试解决这个问题 但我自己似乎无法解决 我正在使用 Web SQL DB 但无法让循环正常使用它 I use for var i 0 i lt numberofArticles 1 i db transaction function
  • Javascript 选择 onchange='this.form.submit()'

    我有一个带有选择和一些文本输入的表单 我希望在更改选择时提交表单 使用以下方法可以正常工作 onchange this form submit 但是 如果表单还包含提交按钮 则当选择更改时 表单不会提交 我猜有某种冲突 我在这里有什么选择
  • Postman - 如何计算 JSON 响应中特定对象的出现次数

    我是 JSON 和 Postman 的新手 我相信我正在尝试做一些非常简单的事情 我创建了一个 GET 请求 它将获得如下所示的 JSON 响应 在下面的例子中我想得到count响应中所有 IsArchived 属性 这些属性的数量因响应而
  • 使用 Lodash 的 TypeScript:_.map(["123", " 234 "], _.trim) 返回 boolean[]?

    我有一个字符串数组 它们已像这样分割 var searchValue 600 800 123 180 var groups searchValue split gt 600 800 123 180 因此项目周围可能存在空格 并且我想删除空格
  • 如何在粘贴时获取文本区域输入字段的新值?

    我发现当我尝试从文本区域字段读取值时onpaste调用函数时 我得到字段的旧值 粘贴操作之前的值 而不是新值 粘贴操作之后的值 以下是此行为的演示 http jsfiddle net qsDnr http jsfiddle net qsDn
  • ThreeJS无法加载Json文件

    首先 我已经读过这个问题 https stackoverflow com questions 17201888 three js exporter export object not working with jsonloader r58没
  • 如何在 JavaScript 中获取浮点数的小数位?

    我想要的是与 Number prototype toPrecision 几乎相反的 这意味着当我有数字时 它有多少位小数 例如 12 3456 getDecimals 4 对于任何想知道如何更快地完成此操作 无需转换为字符串 的人 这里有一

随机推荐

  • for input string:原因及其解决方案

    javascript view plain copy 首先我这里遇到的错误 for input String if 类型 money Double parseDouble 数据库中获取的内容 else if 类型 number Intege
  • StyleGAN新升级

    点击上方 机器学习与生成对抗网络 关注星标 获取有趣 好玩的前沿干货 文章来源 机器之心 编辑部 来自以色列特拉维夫大学的研究者在生成图像方面又有了新的升级 所用方法在保留源图像身份的同时 在细节编辑上实现了更精细的效果 英伟达提出的风格迁
  • 使用动态优先权的进程调度算法 C语言模拟实现 含详细源代码和实验结果

    使用动态优先权的进程调度算法 C语言模拟实现 含详细源代码和实验结果 题目描述 实现对N个进程采用某种进程调度算法 如动态优先权调度 的调度 每个用来标识进程的进程控制块PCB可用结构来描述 包括以下字段 进程标识数ID 进程优先数 PRI
  • CLIP(Contrastive Language-Image Pre-Training)简介

    CLIP Contrastive Language Image Pre Training 利用文本的监督信号训练一个迁移能力强的视觉预训练模型 通过对比学习 训练得到图片和文本的相似度 传闻使用4亿个配对的数据和文本来进行训练 不标注直接爬
  • 打开运行PS、AI等软件时卡在启动窗口的解决办法

    在运行 Adobe Photoshop Illustrator PR等软件时 如果遇到卡在启动页面 可以尝试用下面方法解决 Mac软件资源下载站https mac macsc com id Mjk0ODE5 先将卡住的应用强制退出 左上角
  • 计算机键盘盲打方法,电脑键盘盲打练习方法 盲打键盘指法练习技巧

    小编在小学的时候电脑老师就教过打字 但是那时候还不会盲打 当时各种羡慕会盲打的童鞋 如今已经会盲打了 也想将练习盲打的一些技巧分享给大家 电脑键盘盲打练习方法 盲打键盘指法练习技巧 1 所谓标准指法就是把你的双手依照下图的位置放在键盘上 即
  • chrome - 开发者工具调试技巧

    谷歌浏览器 通常简称为 Chrome 是由谷歌开发的网络浏览器 在开发工作中 Chrome极大的方便开发调试代码 主流的浏览器都内置了 DevTools 掌握 Chrome 的调试技巧 可以提高工作效率 对于测试人员来说 掌握Chrome
  • 测试php中接收call_user_func_array抛出的异常

    1 首先新建一个mytest数据表 表引擎注意使用的innodb CREATE TABLE mytest id int 11 NOT NULL AUTO INCREMENT name varchar 30 NOT NULL DEFAULT
  • Vue的MVVM原理

    MVC 传统的MVC是指用户的操作会调用服务器接口 服务器将处理结果返回给前端Model 然后页面进行重新渲染 MVVM MVVM模型则是指ViewModel通过Data Bindings绑定数据来监听数据的变化 从而更新页面的DOM Vi
  • 如何用html调出磁盘空间,磁盘空间满了?SpaceSniffer 让硬盘空间容量使用一目了然!...

    本来用得好好的电脑 某一天打开之后发现系统盘C盘显示红色 也就是说磁盘空间满了 但是想来想去C盘里也没有什么大的文件 最近也没有安装过什么大型程序 奇怪了 怎么办 C盘下这么多文件如何查找和判断 这里请出非常好用的 SpaceSniffer
  • uniapp小程序分享功能onShareAppMessage函数传参数

    1 使用onShareAppMessage函数 在页面的js文件中定义了 onShareAppMessage 函数时 页面可以表示该页面可以转发 可以在函数中设置页面转发的信息 1 只有定义了该函数 小程序右上角的菜单中才会有转发按钮 2
  • 短视频seo矩阵系统开发者源码打包

    foreach video list as item item video num material model gt getMaterialCountByVideo item dv id 1 item audio num material
  • 在Mac/win10上将Python 3设置为默认

    which python3 alias python usr bin python3 pip3同理 pip3更换为国内镜像 pip3 config set global index url https pypi tuna tsinghua
  • 程序员11月书讯

    10月书讯中奖名单 iorilan 极简 逆流的鱼yuiop mlcjq Marksinoberg 好书推荐 在图灵书讯中选出你认为值得推荐的好书加推荐理由或推荐语 在文末评论里回复 下期书讯更新时 会在本期的书讯评论中选出若干优秀评论 获
  • 一个完整的软件项目管理流程包括什么?有什么软件项目管理工具?

    软件项目管理全流程 及各环节方法 辅助工具 项目的类型不同 其流程必然会不一样 关于通用项目管理的流程其他回答讲述已经非常完善 个人推荐这篇 泛项目管理流程包括哪些 这里就不再赘述 这里要分享的是关于软件项目管理的流程是什么样的 以及我们使
  • 突发!ChatGPT之父警告AI可能灭绝人类,350名AI权威签署联名公开信

    外链图片转存失败 源站可能有防盗链机制 建议将图片保存下来直接上传 img Xij9fMAd 1685974950524 C Users lenovo AppData Local Temp ksohtml3116 wps1 jpg 就在刚刚
  • 多标记学习(大大杂烩,有点乱)

    多标记学习算法 算法分类 按相关性划分 一阶 first order 策略 该类策略通过逐一考察单个标记而忽略标记之间的相关性 如将多标记学习问题分解为 个独立的二类分类问题 从而构造多标记学习系统 该类方法效率较高且实现简单 但由于其完全
  • Unity导入FBX动画文件

    给刚入门的同学稍微记一下fbx格式动画导入unity的步骤 首先直接把fbx文件拖入unity内 如图操作 将avatar骨骼创建之后 我们接下来就根据自己对动画的一些需求设置一下动画的参数 比如截取一下动画的开始帧和结束帧来达到剪切的目的
  • 使用vue实现幻灯片

    1 在父组件中
  • JS事件循环机制(event loop)之宏任务/微任务

    原文地址 Tasks microtasks queues and schedules 视频讲解地址 https www bilibili com video av83625811 from search seid 6644038259868