先来个例子

如果能很快知道执行的顺序结果,那么说明你对这块的内容理解非常深刻。

<div class="parent" data-spm="2.2.2.2"><div class="child">123</div>
</div>
<script>var parent =  document.getElementsByClassName('parent')[0];var child =  document.getElementsByClassName('child')[0];parent.addEventListener('click',function (e) {console.log('parent')},true);child.addEventListener('click',function (e) {console.log('child')})new MutationObserver(function() {console.log('mutate 属性改变');}).observe(parent, {attributes: true});child.click();setTimeout(function () {console.log('定时器0执行了');},1000)setTimeout(function () {console.log('定时器1执行')})setTimeout(function () {console.log('定时器2执行')})new Promise(function (resole) {setTimeout(function () {console.log('promise中的定时器执行')})console.log('promise 执行')resole(1);}).then(function () {console.log('promise 回调执行');parent.setAttribute('data-spm','1.1.1.1');})console.log('最后一行');
</script>

再来展开我们的话题

前言

熟悉JavaScript的小伙伴肯定知道Event Loop(事件循环),由于JavaScript引擎是单线程的,同时只能处理一个任务,所有当JavaScript碰到某些耗时的任务,比如网络IO,为了不阻塞之后的代码执行,这时候就需要将该任务交由其他的线程去执行,并且监听一个事件回调,当异步的结果返回时,将该回调推入到异步的队列中去,而此时假如主线程已经执行完了后续的代码并且处于空闲状态,就会去依次执行异步队列中的代码。比如下面这样

setTimeout(function(){console.log('console start');
});
console.log('console end');// console end
// console start

但是很多时候,JavaScript代码中有很多的“异步”操作,要是他们同时出现了,这时候要怎么判断他们执行的顺序呢。

正文

我们先拟定这么几个有关异步的API

1. setTimeout / setInterval
2. promise
3. Dom listener callback
4. MutationObserver

关于这个可能大家不那么熟悉,不过不影响这篇文章的论述,有兴趣可以移步这篇文章
https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver

开始测试

1. 我们先来测试setTimeout

    setTimeout(function () {console.log('定时器1执行')},0)setTimeout(function () {console.log('定时器2执行')},0)// 定时器1执行// 定时器2执行

两个定时器,延时都为0,先执行1,再执行2,好理解。

   setTimeout(function () {console.log('定时器1执行')},100)setTimeout(function () {console.log('定时器2执行')},50)// 定时器2执行// 定时器1执行

由于定时器2延时较少,所以更快进入异步队列,所以事件循环开始时,先执行定时器2的回调。

2. 测试Promise

    new Promise(function (resole) {console.log('promise 1 执行')resole(1);}).then(function () {console.log('promise 1回调执行');})new Promise(function (resole) {console.log('promise 2 执行')resole(2);}).then(function () {console.log('promise 2回调执行');})// promise 1 执行// promise 2 执行// promise 1回调执行// promise 2回调执行

可以看到,这里new Promise中的操作顺序执行完后,后续的回调也依次执行,没有特别的问题

    new Promise(function (resolve) {setTimeout(function(){resolve(1);console.log('发布回调1');},500)}).then(function () {console.log('promise 1回调执行');})new Promise(function (resolve) {setTimeout(function(){resolve(2);console.log('发布回调2');},100)}).then(function () {console.log('promise 2回调执行');  })// 发布回调2// promise 2回调执行// 发布回调1// promise 1回调执行

这个结果可能有一些匪夷所思,先放着,我们等会回头再来看这个问题。

3. 结合定时器和Promise

setTimeout(function(){console.log('定时器执行了');
})
new Promise(function(resolve){console.log('Promise!')resolve(1);
}).then(function(){console.log('Promise 回调执行');
});
// Promise!
// Promise 回调执行
// 定时器执行了

不知道小伙伴们会不会有疑问,也许觉得定时器应该在Promise的回调之前执行,这时候就可以扯出一些概念了。

一个猜想

主线程执行完后,假如产生了宏任务和微任务,那么他们的回调会被分别推到宏任务队列和微任务队列中去,等到下一次事件循环来到时,若存在微任务,则会先依次执行完所有的微任务,然后再依次执行所有的宏任务。接着进入下一次事件循环。
我们来对比测试3中的例子看看这个猜想。
由于Promise的回调优先定时器执行了,所以可以猜到Promise的回调属于微任务,而定时器属于宏任务。所以虽然定时器先进入异步队列,但是他是进入到宏任务队列,而Promise的回调则是进入到微任务队列,所以事件循环结束后,还是先执行了微任务队列中的Promise回调

再来看测试2中的代码,要注意的是,第一次主线程执行完毕后,只产生了俩个宏任务,即来个定时器的回调,因为还未被resolve,所以微任务还未产生。但是下一次事件循环来临时,定时器2这个宏任务先执行,要注意的是,在这个宏任务完成后,产生了一个新的微任务,即promise 2回调执行,他被马上执行了。随后再执行定时器1这个宏任务,最后执行定时器1产生的微任务。

继续猜想

假如一次事件循环途中,某个宏任务或者微任务产生了一个微任务,那么会立即执行完微任务,然后再依次执行其他的宏任务。执行完毕后,再进入到下一次事件循环。

宏任务和微任务怎么区分

这里我直接给出结论,大家有兴趣可以自行验证或者寻找资料。
setTimeOut ,setTimeInteral,事件回调 这类属于宏任务
promise ,mutation这类属于微任务

验证刚刚开头的例子

我们用以上的猜想来看看开头的例子,一步步分析。
首先给parent和child添加事件监听,然后我们直接执行 child.click(),注意,这里不是通过点击来触发,所以相当于是直接在主线程中执行这个点击方法。

1. 此时父节点在捕获阶段先响应,打印出“parent”
2. 然后子节点在冒泡阶段,打印出“child”
3. 定时器0被推到宏任务队列中,等待下一次事件循环,注意延时为100ms
4. 定时器1被推到宏任务队列中,等待下一次事件循环,注意延时为0ms
5. 定时器2被推到宏任务队列中,等待下一次事件循环,注意延时为0ms
6. 执行Promise中方法,注意这里又产生了一个定时器,推到宏任务队列中,等待下一次事件循环,注意延时为0ms。然后打印出“ promise 执行”。并且resolve后,产生了一个Promise回调的微任务。并且这个微任务中,又产生了一个mutation的微任务
7. 到主线程最后一行代码,打印出 “最后一行”
8. 进入下一次事件循环

所以刚刚开始的打印顺序是 parent,child,最后一行

新的事件循环开始
9. 在上述的第6步,产生了一个微任务,所以先执行,打印出“ promise 回调执行”,并且产生了新的微任务
10.  马上执行新的mutation的微任务,打印出 “mutate 属性改变”。
11. 现在就剩下4个定时器了,按照顺序依次执行,所以分别打印出 “定时器1执行”,“定时器2执行”,“promise中的定时器执行”,“定时器0执行”

最终结果

index.html?_ijt=cms0flsm55m1cgqpqrpd72gub4:15 parent
index.html?_ijt=cms0flsm55m1cgqpqrpd72gub4:18 child
index.html?_ijt=cms0flsm55m1cgqpqrpd72gub4:40 promise 执行
index.html?_ijt=cms0flsm55m1cgqpqrpd72gub4:46 最后一行
index.html?_ijt=cms0flsm55m1cgqpqrpd72gub4:43 promise 回调执行
index.html?_ijt=cms0flsm55m1cgqpqrpd72gub4:21 mutate 属性改变
index.html?_ijt=cms0flsm55m1cgqpqrpd72gub4:31 定时器1执行
index.html?_ijt=cms0flsm55m1cgqpqrpd72gub4:34 定时器2执行
index.html?_ijt=cms0flsm55m1cgqpqrpd72gub4:38 promise中的定时器执行
index.html?_ijt=cms0flsm55m1cgqpqrpd72gub4:27 定时器0执行了

结论

注意到上述步骤6中,promise回调又产生了mutation的微任务,按照之前的猜想,他会被等到下一次事件循环,但是却在定时器1之前执行了,说明他是在这次事件循环被执行的。所以综上猜想,我们最后可以给出这样的结论

第一次主线程执行完后,当前如果有微任务,则先执行完所有的微任务。宏任务就推到宏任务队列中,等待下一次事件循环。然后进入下一次事件循环
然后,每执行完一个宏任务后,检测当前是否有微任务产生,有就立即执行所有微任务。有宏任务产生,则推到宏任务中,等待下一次事件循环。
接着依次重复执行完本次事件循环中的所有宏任务。然后进入下一次事件循环

上面的结论,略显繁琐,其实假如我们把第一次主线程的执行,即script下的所有代码,看成是第一次宏任务,那么核心结论可以变成这样

假如主线程执行栈不为空,那么执行完所有宏任务后,再执行所有微任务
否则,执行完一个宏任务后,会立即执行所有微任务

以上测试均在Chrome中进行。

后记

其实这些东西主要是要在脑子中形成一个印象,用来加深理解 事件循环 。而且,在Node的世界里,比如还会有其他的微任务,比如Process.nextTick。弄懂这些,对JavaScript的执行能有更好的把握。

JavaScript中的宏任务和微任务相关推荐

  1. 【JS】JavaScript中的宏任务和微任务

    目录 概念 宏任务 微任务 运行机制 总结 js是一门单线程语言,所以它本身是不可能异步的,但是js的宿主环境(比如浏览器.node)是多线程,宿主环境通过某种方式(事件驱动)使得js具备了异步的属性 ...

  2. 事件循环中的宏任务和微任务执行顺序

    事件循环中的宏任务和微任务执行顺序 先来了解一下事件循环.宏任务.微任务和Promise 1.事件循环(Event Loop)运行机制 执行一个宏任务(栈中没有就从事件队列中获取) 执行过程中如果遇到 ...

  3. ❤️一起谈一谈js中的宏任务和微任务!

    前面面试的文章中我们说过一道关于宏任务和微任务的题: setTimeout(function(){console.log('1') });new Promise(function(resolve){c ...

  4. JS事件循环中的宏任务和微任务执行顺序

    1. 宏任务和微任务事件 其中微任务的优先级高于宏任务,括号内为事件运行环境 宏任务 微任务 I/O事件/onClick点击事件 process.netTick (Node) setTimeout N ...

  5. js 中的 Event Loop 以及 宏任务 与 微任务

    目录 前言 1.JS 的 执行引擎 与 执行环境 2.js 是单线程的 一.事件循环(Event Loop) 二.任务队列 三.宏任务 与 微任务 1.宏任务 2.微任务 3.宏任务与微任务的运行机制 ...

  6. js---对事件循环宏任务和微任务的理解

    一.JS特点 Js作为一门单线程语言,即一次只能完成一个任务,当有多个任务时,任务就得进行排队等待执行,只能等待自己的前一个任务执行完成后自己才能执行. 二.JS事件循环 要理解JS的事件循环的就必须 ...

  7. JavaScript中的微任务和宏任务

    在正常情况先下,JavaScript任务是同步执行的,即执行完一个前一个任务,然后执行后一个任务.也就是跟着主线任务按序执行. 只有遇到异步任务的情况下,执行顺序才会改变.这时,需要区分两种任务:宏任 ...

  8. js中的事件循环和宏任务和微任务的理解

    参考许多大神的文章,写下自己的理解 事件循环: 说到事件循环就得谈到js中同步和异步的执行顺序 1.javascript将任务分为同步任务和异步任务,同步任务放到主线中,异步函数首先进行回调函数注册. ...

  9. 弄懂 JavaScript 执行机制,宏任务和微任务

    本文的目的就是要保证你彻底弄懂javascript的执行机制. 不论你是javascript新手还是老鸟,不论是面试求职,还是日常开发工作,我们经常会遇到这样的情况:给定的几行代码,我们需要知道其输出 ...

最新文章

  1. The J2EE Architect's Handbook讀書筆記(二)
  2. Xcode 7中Static Cells自动计算高度失效的解决方法
  3. 自己动手写一个单链表
  4. VueX(Vue状态管理模式)
  5. 消息中间件选型分析——从Kafka与RabbitMQ的对比来看全局
  6. 降维后输入分类器分类时报错_逻辑回归解决多分类方法及其优缺点分析
  7. 作者:宾军志(1976-),男,御数坊(北京)科技咨询有限公司联合创始人。...
  8. 一个文件合成器的代码
  9. fullcalendar 数据渲染 背景色_数据可视化设计,从0到1必备技能
  10. 打包图片上传cdn_Media Buy之Landing Page的资源文件CDN部署方案
  11. kubernetes 升级到1.6
  12. 阿里矢量库图标在线链接的使用方法,引入,改变大小与颜色
  13. 【虚拟机数据恢复】误删除VMware虚拟机vmdk文件的数据恢复案例
  14. 如何将硬盘克隆到固态硬盘,固态硬盘系统克隆怎么弄
  15. Linux 添加一块新硬盘
  16. 神经网络建模的基本思想,人工神经网络建模步骤
  17. Win10 IoT C#开发 2 - GPIO Pin 控制发光二极管
  18. eclipse 提示destination folder must be accessible
  19. 阿里云视频点播服务(上传,删除,获取播放地址,获取播放凭证)
  20. Word怎么删除空白页操作方法 Word删除空白页的办法详解oldtimeblog

热门文章

  1. springboot 、thymeleaf、pagehelper 、springsecurity实现 登录,用户认证,分页的前端使用妹子UI
  2. 手把手教你给偶像刷票。偶像来了?程序员来了!Charles实战
  3. python数据类型哪些是无序的_Python自学知识-Python中的数据类型有哪些?
  4. 室内导航的突破性进展—懒图科技提供低成本的室内导航服务
  5. 使用 CSS 媒体查询创建响应式网站
  6. input标签单行文本域type=text的可以添加属性以及其描述
  7. matlab 与latex结合,TeX系列: MATLAB和LaTeX结合绘图
  8. VIPCARD微信会员开多行业多开SAAS系统
  9. 吕毅谈大型组织应用Scrum的经验(转)
  10. UiPath选择日期