[回顾]事件循环机制 (Event-loop)

1. JS的运行环境

js运行的环境咱们称之为宿主环境,目前有三种运行环境,一种运行在浏览器(javaScript),一种运行在服务端(nodejs),另外一种是运行在咱们的客户端(好比Vscode客户端就是使用js写的),所以只要给js配备的相应的执行引擎,js能够运行在任何环境java

2. 浏览器的宿主环境

  1. JS引擎node

    • 负责执行执行栈的最顶层JS代码
    • 和 GUI 渲染线程互斥,JS 运行耗时过长就会致使页面阻塞。
  2. GUI引擎web

    • 负责渲染页面,解析 HTML,CSS 构成 DOM 树等,当页面重绘或者因为某种操做引发回流都会调起该线程。
    • 和 JS 引擎线程是互斥的,当 JS 引擎线程在工做的时候,GUI 渲染线程会被挂起,GUI 更新被放入在 JS 任务队列中,等待 JS 引擎线程空闲的时候继续执行。
  3. 事件监听线程 (DOM事件,window窗口事件等等)ajax

    • 当事件符合触发条件被触发时,该线程会把对应的事件回调函数添加到事件队列的队尾,等待 JS 引擎处理。
  4. 计时线程(SetTimeout、setInterval 计时器)api

    • 开启定时器触发线程来计时并触发计时,计时完毕后,将计时器结束的回调函数添加到事件队列中,等待JS引擎空闲后执行,等待 JS 引擎处理。
    • 浏览器定时计数器并非由 JS 引擎计数的,阻塞会致使计时不许确。
  5. 网络线程:(ajax网络请求)浏览器

    • http 请求的时候会开启一条请求线程。
    • 请求完成有结果了以后,将请求的http回调函数添加到任务队列中,等待 JS 引擎处理。

3. 事件队列(任务队列/消息队列)

事件队列在不一样的宿主环境中有所差别,大部分宿主环境会将事件队列进行细分。在浏览器中,事件队列分为两种:bash

  • 宏任务(队列):macroTack、计时器结束的回调、事件回调、http回调等等绝大部分异步函数进入宏队列
  • 微任务(队列):MutationObserver,Promise产生的回调进入微队列

MutationObserver 用于监听某个DOM对象的变化网络

当执行栈清空时、JS引擎首先会将微任务中的全部任务依次执行结束,若是没有微任务,执行宏任务数据结构

2. 事件循环(Event Loop)

事件循环分为三个部分,分别由 浏览器宿主,web api 与 事件队列(也称任务队列)组成异步

3. 事件循环机制

  • 执行栈

    因为JavaScript 引擎是单线程,同一时间只能执行一个任务,其余任务都得按照顺序排队等待被执行,只有当前的任务执行完成以后才会往下执行下一个任务,所以这些任务被排队在一个单独的地方。这个地方被称为执行栈

    执行栈是一个后进先出数据结构,用于存放各类函数的执行环境,每个函数执行以前,它的相关信息会加入到执行栈,函数调用以前,建立执行环境,而后push到执行栈;函数调用以后,销毁执行环境,并从执行栈顶部推(pop)出去

  • 同步任务、异步任务

    同步任务:当一个脚本第一次执行的时候,js引擎会解析这段代码,并将其中的同步代码按照执行顺序加入执行栈中,而后从头开始执行。若是当前执行的是一个方法,那么js会向执行栈中添加这个方法的执行环境,而后进入这个执行环境继续执行其中的代码。当这个执行环境中的代码 执行完毕并返回结果后,js会退出这个执行环境并把这个执行环境销毁,回到上一个方法的执行环境。。这个过程反复进行,直到执行栈中的代码所有执行完毕。 一个方法执行会向执行栈中加入这个方法的执行环境,在这个执行环境中还能够调用其余方法,甚至是本身,其结果不过是在执行栈中再添加一个执行环境。这个过程能够是无限进行下去的,除非发生了栈溢出,即超过了所能使用内存的最大值。

    异步任务:js引擎遇到一个异步事件后并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其余任务。当这个异步事件返回结果后,js会将这个事件加入与当前执行栈不一样的另外一个队列,咱们称之为事件队列事件队列(事件队列是先进先出的数据结构),被放入事件队列不会马上执行其回调,而是等待当前执行栈中的全部任务都执行完毕。异步函数的执行事件,会被宿主环境控制。

  • 事件循环(Event Loop)

    主线程处于闲置状态时,主线程会去查找事件队列是否有任务。 若是有,那么主线程会从中取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,而后执行其中的同步代码…,如此反复,这样就造成了一个无限的循环。JS引擎对事件队列的取出执行方式,以及与宿主环境的配合,称之为事件循环。

举个栗子

<script>
        function a() {
            console.log("a")
            b();
        }

        function b() {
            console.log("b");
            c();
        }

        function c() {
            console.log("c")
        }

        console.log("global");
        a();
    </script>

复制代码

运行流程:在JS代码执行以前<script>,首先会初始化一个全局执行上下文(也称执行环境),并push到栈顶,接下来开始在全局执行环境下,执行代码,首先将三个函数定义,提取到全局上下文里去,而后继续执行,等执行到console.log('c')以后,会调用console对象里的一个log函数,(调用任何一个函的时候,都会为这个函数建立一个函数执行上下文),而后建立log的执行上下文,并push入栈,而后就能够运行这个函数了,(始终记住js引擎永远执行的是执行栈的最顶部),运行该函数后,控制台输出一个"global",而后该函数运行结束,销毁该执行上下文,并从栈顶被推出(出栈),此时又回到了全局执行环境之下继续执行,此时又遇到了a函数的调用,调用该函数,建立一个a的函数执行上下文,并push入栈,而后开始执行a函数里面的代码,在a函数里又遇到里console对象里的log函数,又为这个log函数建立一个log的函数执行上下文,并push到栈顶,而后运行log函数,控制台输出a,以后log函数执行结束,销毁log的上下文,回到a的函数执行上下文上,继续运行,而后又发现b函数的调用,此时建立b的函数执行上下文,并push入栈,而后运行b函数,在b函数里发现console对象里的log函数的调用,而后继续为这个log函数建立log的函数执行上下文,并push入栈,而后运行该log函数并在控制台输出"a",此时该log函数执行结束,销毁其log的上下文,并从栈顶被推出(出栈pop),此时又回到了b的函数执行上下文环境下执行,继续运行,发现了c函数的调用,而后为c函数建立C的函数执行上下文,并push入栈,而后开始执行c里面的代码,执行代码过程当中,发现了console对象里的log函数被调用,又为这个log函数建立一个log的函数执行上下文,并push到栈顶,在该log环境里,运行该函数,输出"c",以后该log函数运行结束,销毁其log上下文,并从栈顶被推出(出栈pop),而后回到c的执行环境中,此时已经没有能够运行的数据了,则c运行结束,销毁c的函数执行上下文,并从栈顶被推出(出栈pop),而后回到b的执行环境之中,c的调用结束以后,b也没有能够执行的代码,此时b函数也运行结束,则销毁b的函数执行上下文,并从栈顶被推出(出栈pop),此时回到a的执行环境执行,a函数在b函数的调用结束后也没有可再执行的代码,所以a函数也执行结束,销毁a的函数执行上下文,并从栈顶被推出(出栈pop),最终执行权又回到了全局执行环境里,在全局执行环境里,发现已经没有可运行的代码了,而后也销毁全局执行上下文,并从栈顶推出(出栈pop),此时执行栈里面都为空,若是事件队列有事情,则会把执行事件队列里的代码。

相关文章
相关标签/搜索