专栏首页前端与Java学习JS中的事件循环机制与宏队列、微队列笔记
原创

JS中的事件循环机制与宏队列、微队列笔记

01-基本概念

1.1-JavaScript是单线程语言

为什么JavaScript是一门单线程语言?作为一门浏览器脚本语言,它的主要用途就是操作DOM和与用户交互设计,如果说js是多线程的话,那么它在操作DOM的时候,一个线程对DOM进行了新增操作,另一个线程对DOM进行了删除操作,那么这个时候js的处理将会变得十分复杂。为了避免这种情况,JavaScript一诞生就是单线程。

1.2-任务队列

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。所有任务可以分成两种,一种是同步任务,另一种是异步任务。

同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;所有同步任务都在主线程上执行,形成一个执行栈;

异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

也就是说:当主线程将执行栈中所有的代码执行完之后,主线程将会去查看任务队列是否有任务。如果有,那么主线程会依次执行那些任务队列中的回调函数。

1.3-事件循环

主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。

主线程在运行的时候,产生堆和栈,栈中的代码调用外部的API,它们会在“任务队列”中加入各种事件。只要栈中的代码执行完毕,主线程就会去读取“任务队列”中的回调函数依次执行。

1.4-宏队列和微队列

在任务队列中,其实又分为宏队列和微队列,他们的执行优先级也有区别,那么哪些回调函数放在宏队列,哪些回调函数放在微队列呢?

宏队列:dom事件回调、ajax回调、定时器回调

微队列:promise回调、mutation回调

因此JS执行时首先必须执行所有的初始化同步任务代码,执行完以后,每次准备取出第一个宏任务执行之前,都要将所有的微任务一个一个取出来执行。

让我们看一个案例帮助我们理解,例1

setTimeout(() => {
    console.log('timeout callback()1');
    Promise.resolve(3).then(
        value => {
            console.log('Promise onResolved3()', value);
        }
    )
}, 0)

setTimeout(() => {
    console.log('timeout callback2()');
}, 0)

Promise.resolve(1).then(
    value => {
        console.log('Promise onResolved1()', value);
    }
)

Promise.resolve(2).then(
    value => {
        console.log('Promise onResolved2()', value);
    }
)

上面的代码中写了两个setTimeout定时器函数,在里面写入了打印输出的回调,以及两个状态为resolved的Promise对象,在then方法中写入了两个打印输出的回调,我们已经知道了宏队列和微队列的执行流程,那么我们来分析一下上面代码的执行流程。

  • 1.首先看上面的代码中有没有同步代码任务,发现没有可以直接对任务队列中的异步回调进行分析。
  • 2.setTimeout定时器的回调函数将会放入宏队列中,而Promise中的回调将会放入微队列中。
  • 3.我们知道执行第一个宏任务之前都要把微队列的任务全部取出执行完毕才能执行宏任务,因此可以分析出,上面代码将会优先打印出'Promise onResolved1()', 1'Promise onResolved2()', 2
  • 4.当微队列中的任务执行完开始取出宏队列中的第一个宏任务执行,也就是第一个setTimeout中的回调函数,因此将会打印'timeout callback()1'
  • 5.因为执行完这个打印输出之后,下面又有一个立即resolved的Promise对象,所以我们需要把这个Promise中的回调再次加入微队列中。
  • 6.现在宏队列还有一个定时器回调,微队列中又多了一个微任务,因此我们需要先执行微队列中的回调,所以将会打印输出'Promise onResolved3()', 3
  • 7.微队列中的回调执行完毕后,再执行宏队列中的任务,因此最后打印timeout callback2()

所以最后的打印顺序应该是:

Promise onResolved1() 1
Promise onResolved2() 2
timeout callback()1    
Promise onResolved3() 3
timeout callback2() 


看完上面这道简单的例子,我们再来看一下第二道比较复杂的题,看看能否判断出正确的输出顺序,例二

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

new Promise((resolve, reject) => {
    console.log('1');
    resolve();
}).then(() => {
    console.log('2');
    new Promise((resolve, reject) => {
        console.log('3');
        resolve()
    }).then(() => {
        console.log('4');
    }).then(() => {
        console.log('5');
    })
}).then(() => {
    console.log('6');
})

new Promise((resolve, reject) => {
    console.log('7');
    resolve();
}).then(() => {
    console.log('8');
})

分析:

1、从上往下看没有同步代码,首先将定时器中的回调添加到宏任务队列中,所以现在宏任务队列中的任务[0]

2、再执行Promise代码,将先同步代码所以打印1,然后状态立马变成resolve,所以将其中的异步回调函数打印代码加入到微队列中[2]

3、微队列中2还没有打印取出,所以我们再看下一个Promise中的代码,先将打印同步代码7,然后立即变为resolve状态,并将异步回调打印8的代码放入微队列中所以现在的微队列是:[2,8]

4、所有的执行完之后我们要先把微队列中的代码都取出执行完再去执行后面的代码以及宏队列的代码,所以先取出2,即打印2,所以现在的微队列只有一个任务[8]

5、所以现在已经打印了1,7,2,打印完2以后我们再从这一行代码往下看又new了一个Promise对象,里面有同步代码打印3,然后立即变为resolve状态,因此将4放入微队列[8,4]

6、接下来这一步要非常注意:在我们没有打印4的时候,那么我们是不会把后面then方法中的5放入微队列中的,我们会先将外层Promise中的then中的6放入微队列,因为内层的Promise已经执行完最后一个then方法了,因此现在的微队列是[8,4,6]

7、现在开始取出微队列中的任务进行执行,将依次打印8,打印完8后面没有其余代码,因此急需取出打印4的任务再打印4,当打印完4,之后那么就会将后面then中的打印5的异步任务放入微队列,因此现在的微队列是[6,5],目前的打印顺序是1,7,2,3,8,4

8、最后我们只需要将微队列中剩余的任务和宏队列中剩余的任务取出执行便分析完了整个顺序流程,因此最后的打印顺序应该是1,7,2,3,8,4,6,5,0

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

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

我来说两句

0 条评论
登录 后参与评论

相关文章

  • ES6中的Promise深入学习

    Promise是异步编程的一种新的解决方案,比传统的解决方案(纯回调函数)更加的方便和强大。简单来说,Promise就是一个容器,里面保存着某个未来才会结束的事...

    帅的一麻皮
  • 移动端轮播图笔记

    移动端浏览器兼容性好,我们不需要考虑以前JS的兼容问题,可以放心使用原生JS书写效果,但是移动端也有自己独特的地方,比如触屏事件touch(也叫触摸事件),An...

    帅的一麻皮
  • 前端移动web-day03学习笔记

    rem:参考的是根元素(html)字体大小 (统一的,一个页面只有一个html)

    帅的一麻皮
  • Java并发学习4【面试+工作】

    Fork/Join框架是Java7提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。

    Java帮帮
  • 订单自动过期实现方案

    这个太简单了,就是在查询的时候判断是否失效,如果失效了就给他设置失效状态。但是弊端也很明显,每次查询都要对未失效的订单做判断,如果用户不查询,订单就不失效,那么...

    Mshu
  • Java的ThreadLocal

    ThreadLocal是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状...

    用户3467126
  • 找工作的一些感悟——前端小菜的成长

    http://www.cnblogs.com/dolphinX/p/3510346.html

    bear_fish
  • UEditor初始化失败(实例已存在,但视图未渲染出来,单页化)

    今天调整项目需求,里面涉及到需要初始化多个百度编辑器。但是遇到这样一个问题,百度编辑器在第一次进入时,编辑器容器未初始化,再次刷新容器初始化成功。 通过UE.i...

    Clearlove
  • 【小技巧】同步VSCode设置及扩展插件,换机不用愁!

    有了这个技能,换电脑,换工作等就不用愁了。 实现同步的功能主要依赖于VSCode插件“Settings Sync”。它是基于 GitHub Tokens 和 ...

    Dunizb
  • 解码大脑:在脑机接口上寻求稳定性

    脑机接口(BCI) 寻求在神经科学和工程系统之间建立桥梁,使神经工程师能够记录大脑中的电活动,对其进行分析以推断个人正在尝试做什么,并使用它来控制假肢等设备。除...

    脑机接口社区

扫码关注云+社区

领取腾讯云代金券