JavaScript事件循环

写在前面

提及javascript(如下简称js)这门语言,相信你们已经很是熟悉了,无论是前端开发仍是后端开发几乎无时无刻都要跟它打交道。虽然说开发者天天几乎都要操做js,可是你真的肯定你掌握了js的运行机制吗!下面咱们就来聊聊这话题。javascript

JavaScript运行机制图解

clipboard.png

上图咱们能够分为两部分:浏览器中的JS引擎运行环境Runtime,那它们的区别是什么?前端

  • JS引擎:编译并执行代码的地方。

    如上图中能够看出JS引擎分为两大核心部分:栈和堆java

    栈(Stack):js代码的执行都要压到此栈中执行。 ajax

    堆:存放对象、数组的地方,js垃圾回收就是检查这里。后端

  • Runtime:浏览器的运行环境,它提供了一些对外接口供JS调用,如网络请求接口。

JavaScript引擎是单线程的

JS引擎是单线程的,也就是说在一个时间段内,事情只能一件一件的按前后顺序去作,第一件事没作完就不能第二件事。那么在js引擎中负责解释和执行js代码的线程只有一个,咱们能够称之为主线程数组

固然浏览器的运行环境Runtime还提供一些其余的线程,如定时器线程、ajax线程、事件线程、网络请求和UI渲染的线程,为了和js主线程分开,咱们这里都统称它们为工做线程浏览器

因为浏览器是多线程的,因此工做线程和js主线程均可以执行任务,线程间互不干扰。网络

JavaScript同步(异步)任务

在JavaScript任务能够分为两种:多线程

  • 同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务,若前一个任务耗费很长时间,则后面的任务会一直处于等待状态,即阻塞状态。
  • 异步任务:在栈执行代码的过程当中,如遇到异步函数,如setTimeout、异步Ajax、事件处理程序,会将这些异步代码交给浏览器的工做线程来处理,咱们把这些任务称之为异步任务。异步任务是不进入主线程,而是进入任务队列(queue task)。异步

    • 什么异步函数?

      异步函数一般是由发起函数回调函数构成的。如:

      A(callback)

      • 函数A就是发起函数
      • callback就是回调函数
​

它们都是在主线程调用的,其中发起函数用来发起异步过程,回调函数用来处理结果。

如:`setTimeout(callback,1000)`

setTimeout就是发起函数、callback就是回调函数。

如:异步的Ajax
var xhr = new new XMLHttpRequest();
    xhr.onreadystatechange = callback; //callback为回调函数
    xhr.open('get',url,true);
    xhr.send(null); // send为发起函数

能够看出发起函数和回调函数也能够是分离的。

既然同步任务是在主线程中执行的,那么异步任务什么时候执行?

答:是这样的,一旦栈中同步任务执行完毕后,系统就会经过事件循环机制读取任务队列中的任务一个个移到栈中去执行。

事件循环

当主线程中的任务执行完毕后,会从任务队列中获取任务一个个的放在栈中执行去执行,这个过程是循环不断的,因此整个的这种运行机制又称为事件循环。

在js中,代码最终都是在栈中执行的,栈结构的特色是:先进后出,后进先出

咱们来看下面代码的运行结果:

function bar(){
    console.log(1);
    foo();
}

function foo(){
    par();
    console.log(3);
}

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

bar();

运行的最终结果是:132。 为何结果不是123呢?

下咱们来分析下代码运行时入栈和出栈的过程。

首先当调用函数bar()时,此函数就会先入栈,其内部的console.log(1)也会随之入栈执行。

clipboard.png

执行完console.log(1)后,就要出栈,因而控制台先打印出结果1,只剩下bar()在栈中。接着再执行函数bar内部的函数foo,因而函数foo也开心的入栈了。

clipboard.png

执行函数foo的内部代码,调用函数par(),因而函数par()也要跟着入栈。

clipboard.png

因为函数par()内部执行遇到了异步函数setTimeout,异步函数则会由浏览器的Runtime运行环境的工做线程来处理,等定时器设置的时间到达就会被放到任务队列中,此时栈的同步任务继续执行。

clipboard.png

接着在执行par函数中的console.log(3),控制台打印结果为3 ,此时栈的代码执行完毕后,会按照栈的特色进行

先进后出,后进先出顺序进行出栈。出栈顺序:先函数par()-->后函数foo()-->最后函数bar

最后只剩下异步任务,由主线程去获取任务队列中的任务放在栈中去执行。也能够认为栈中的同步代码执行老是在读取异步任务以前执行。

clipboard.png

最后执行setTimeout中的回调函数:结果控制台输出为2。

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

因此代码的最终运行结果为132。

小结

  • js引擎是单线程执行js代码,同步任务在栈中按顺序执行,若是某一个同步任务没有执行完毕,则后面的代码将会处于阻塞等待状态
  • 栈中若执行遇到了异步任务(如定时器、异步Ajax、事件),会将此异步任务经过浏览器对应的工做线程来处理。
  • 工做线程中的全部异步任务均会按照设定的时间进行等待,时间一到会被加入任务队列。若是是异步ajax,则等待其返回结果后在加入到任务队列
  • 当栈中为空时,会经过事件循环来一个个获取任务队列中的任务放到栈中进行逐个运行。即栈中的同步任务老是在读取异步任务以前执行
  • 定时器设置的时间不必定按照设定的时间进行执行,这得取决于栈中同步任务耗费的时间。由于栈中执行的同步任务若是耗费很长时间,则会影响到异步任务回调函数的执行。