关于Event Loop

前言

Event Loop也就是事件循环,是个老生常谈的问题了,而且都是些概念性的东西,略显枯燥,所以我也一直没有对这块进行过整理,奈何面试官总是喜欢去考这一块的知识,没办法只能硬着头皮进行学习,这篇文章也是对我学习到的一点关于Event Loop的只是进行的总结

JavaScript是一门单线程语言

大家都知道,JS是一门单线程非阻塞的语言,在执行任何任务的时候,都通过一个主线程来进行。试想一种情况: 如果JS是多线程的原因,那么同时有两个线程,一个线程对dom元素添加事件,另一个线程则是删除这个dom,那么这种情况要如何处理呢?
为了避免这样的情况发生,当时的布兰登(JavaScript创造者)只能将其设计成单线程非阻塞的语言,用以确保操作上的统一性。
也正是因为JS的单线程特性,也就是所有操作都进行排队,每次都只能执行一个任务,那么为了保证一些耗时较长的操作不会阻塞后续任务的执行,应该如何做呢? 当然就是使用Event Loop机制了。
但是由于在浏览器中的Event Loop是各大厂商根据规范自行实现的,而node中的Event Loop是根据libuv这个库实现的,所以会有不同,因此要分开进行讨论。

浏览器中的事件循环机制

  • 关于调用栈和异步事件处理
    • 调用栈
      首先我们来看如下代码,最开始声明了两个变量a和b,然后按照顺序分别打印出他们:


      image.png

      那么JS是如何执行他们的呢?
      答案是进行排队,然后按照顺序调用,这个排队的地方就是调用栈,上面代码的调用栈顺序如下:


      image.png

      执行完毕后,该调用栈被销毁,之后开始下一个调用栈的堆栈和执行,这个过程不断重复就是事件循环。
      • 异步事件处理
        讨论这点的时候我们先抛开Promise以及async/await,只从最开始的setTimeOut(setIntervalsetTimeOut机制类似,所以不讨论)来看,大家都知道setTimeOut是浏览器自行实现的用于处理异步操作的方法,用法如下,打印结果是123:
        image.png

        上面代码的意思是先打印一个1,然后将console.log(3)放入到下一个事件循环的调用栈的首位,然后打印一个2,到了下一个事件循环期间,再打印一个3,如下图所示:
        image.png

        在该例子中,最多只是存在两个队列,但是后来ECMAScript设计组推出了Promiseasync/await,这种情况就产生了变化,因为他引入了一个新的队列。
    • macro task和micro task
      macro task一般叫做宏任务,micro task叫做微任务,上面例子讨论的只是在宏任务阶段所发生的事,而在Promiseasync/await被引入后,也同时引入了微任务的概念。也就是说setTimeOut和同步的操作是属于宏任务阶段的,而Promiseasync/await是属于微任务队列的。
      那么什么是微任务呢?
      微任务实际上就是另外一个队列,这个队列中被放入的操作是在前一个宏任务阶段的调用栈中的操作被执行完毕后就立刻开始执行的,执行的顺序为:

宏任务队列 -> 微任务队列 -> 宏任务队列

那么也就是说Promiseasync/await的操作会在setTimeOut的操作之前被执行。
来看下面例子:

image.png

可以很清晰的看出来,先是打印1,然后打印Promise中2,最后才是打印setTimeOut中的3,虽然他们的顺序是反过来的,反映到图就是如下:
image.png

node中的事件循环

node中的事件循环是基于libuv库实现的,所以与上面浏览器中的会有所不同,两者不可混为一谈。
并且在node中,存在的处理异步的方法比浏览器要多,除了setTimeOutsetInterval以及Promiseasync/await之外,还存在setImmediateprocess.nexttick这两种方法,他们的执行时机也有所不同。
首先还是来看看node中事件循环的阶段吧。

  • 阶段
    在node中,事件循环分为如下几个阶段:


    image.png

    他们对应的作用如下:

    • timers: 执行setTimeOutsetInterval中的操作
    • I/O callbacks: 执行系统操作回调事件,例如TCP报错等
    • idle, prepare: 该阶段用于内部使用,不用管
    • poll: 执行同步代码,注意这是启用事件队列时候首先进入的阶段,而不是timers,node有可能会被阻塞在该阶段
    • check: 执行setImmediate中的操作
    • close callbacks: 执行一些事件关闭的回调操作,例如socket.on(close, xxx)的回调
  • 执行顺序

    • 首先进入到poll阶段,查看poll队列中是否有任务,有任务的话就按照先进先出的顺序进行执行,当任务执行完毕,poll队列为空时候又或者是poll计时器到达最大限制时候(这个最大限时不是固定的),就将setTimeOutsetInterval中的操作放入到timers队列中,将setImmediate中的操作放入到check队列中,然后进入check阶段
    • check
      执行check队列中的setImmediate中的操作
    • close callbacks
      执行时间关闭回调
    • timers
      执行timers队列中setTimeOutsetInterval中的操作
    • I/O callbacks
      执行系统操作回调事件
  • setTimeOut执行超时问题
    setTimeOut的第二个参数用于设置执行延后的时间,以毫秒(ms)为单位,但是这个延时的操作其实并不准确,因为在poll阶段有可能因为被阻塞而导致延后时间边长,比如下面的例子就可以看出延时到了2800多毫秒才执行了setTimeOut中的操作:

    image.png

    并且上面这个理由有个有趣的地方,当我不对数字n进行访问的时候,延时是2800多毫秒,当我在setTimeOut中对n进行访问的时候,延时就变成了23000多毫秒,多了将近十倍:
    image.png

    这个也是说明了在不访问n的时候,poll阶段的操作其实并没有执行完,而是因为poll的计时器到达了最大值,所以poll阶段被强制终止进入了后续阶段,而我访问n的时候,这个计时器的功能就失效了,一定会算出n才会进行下一步,我猜测这也是为了保证操作的准确性而做出的优化吧。

  • setTimeOut和setImmediate执行顺序
    关于这两者最重要的就是执行顺序问题了,其实从上面node的事件循环阶段就已经能够看出来了,setImmediate是在setTimeOut之前执行的,因为setImmediate是在poll后面的check阶段执行,而setTimeOut是在timers阶段执行,但是实际情况却有所不同,请看下面例子:

    image.png

    这是因为在主线程中,回调的执行顺序取决于当前进程的性能,所以顺序上会有不同,但是当我们将这两者都放入到同一个回调中去执行的时候,他们的顺序就能保证了,如下:
    image.png

  • 关于process.nextTick
    另外在node中还有一个process.nextTick用于推迟操作,这个process.nextTick不存在于上面任何一个阶段,他只在当前阶段(无论是timers亦或是check等)结束后立即执行,可以从下面两个例子中看出:

    image.png

    image.png

后记

关于Event Loop的总结就到这里为止,这些在实际工作中用到的可能较少,面对面试时,主要还是在于区分好浏览器还是node环境,阐述清楚概念以及举出几个针对setTimeOutPromiseasync/awaitsetImmediateprocess.nextTick相关执行顺序即可。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,548评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,497评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,990评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,618评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,618评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,246评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,819评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,725评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,268评论 1 320
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,356评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,488评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,181评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,862评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,331评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,445评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,897评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,500评论 2 359

推荐阅读更多精彩内容