javascript的事件循环(event loop)

前言:

 

JS是单线程的,那么,它如何处理异步操作?

   答:使用事件循环,执行过程如下:

  • 所有同步任务都在主线程上执行,形成一个执行栈

  • 主线程之外,会存在一个任务队列,只要异步任务有了结果(如:setTimeout的等待时间到了),就在任务队列中放置一个事件(所以,也叫事件队列),进行排队(处于等待状态)。

  • 当执行栈中的所有同步任务执行完后,就会读取任务队列(事件队列)中的任务(事件)。即:任务队列中的任务就结束了等待状态,进入执行栈。

  • 主线程不断重复第三步。直到任务队列和执行栈里的代码执行完毕。

 

1、执行栈

       注意,执行栈不是内存的栈区。要了解堆栈和内存的分区,请查看js的基本类型(值类型)和引用类型的区别。

       在js中,当很多函数被依次调用的时候,因为js是单线程的,同一时间只能执行一个函数,怎么办?不能同时来,得排个队,排个一子队,按照顺序来,那这个一子队的顺序,即哪个函数在前,哪个函数在后,谁来记录呢?js为此专门开辟了一个内存区域,起名叫做执行栈。在执行栈里,保存着即将要执行的函数。就像大家在学校里排队打饭一样,只有一个人给你打饭时,你就老老实实地排队,一个打完饭了,下一个再打。

       当一个脚本第一次执行的时候,js引擎会解析这段代码,并将其中的同步代码按照执行顺序加入执行栈中,然后从头开始执行。如果当前执行的是一个函数,那么js会向执行栈中添加这个函数的执行环境(当我们调用一个函数的时候,js会生成一个与这个函数对应的执行环境(context),又叫执行上下文。这个执行环境中保存着这个函数的私有作用域,上层作用域的指向,方法的参数,这个作用域中定义的变量以及这个作用域的this对象。),然后进入这个执行环境继续执行其中的代码。当这个执行环境中的代码 执行完毕并返回结果后,js会退出这个执行环境并把这个执行环境销毁,回到上一个方法的执行环境。。这个过程反复进行,直到执行栈中的代码全部执行完毕。

 

2、事件队列

      以上说的是同步执行的情况,如果出现了异步(如发送ajax请求数据)执行,就需要用到事件队列,或者叫做任务队列(Task Queue)。

       js引擎遇到一个异步事件后并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中下面的任务。执行过程中,当这个异步事件被触发时(异步任务有了结果,如setTimeout的等待世间到了),把该事件加入一个队列里,我们称之为事件队列。被放入事件队列不会立刻执行其回调,而是等待当前执行栈中的所有任务都执行完毕, 主线程处于闲置状态时,主线程会去查找事件队列是否有任务。如果有,那么主线程会从中取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,然后执行其中的同步代码...,如此反复,这样就形成了一个无限的循环。这就是这个过程被称为“事件循环(Event Loop)”的原因。

         即:执行栈存放所有要执行的函数(代码),事件队列中存放所有的异步任务,如果执行栈中的代码执行完毕后,才会把事件队列中的事件(所有异步的代码)放入执行栈,接着执行。

          这也是为什么如下代码的执行,与你想象的不一样的原因所在。

              这个代码

              setTimeout(function(){

                         console.log(1);

                 }, 0)

                 console.log(2);

             的执行结果是在后台分别打印出: 2     1

javascript的事件循环(event loop)_第1张图片

javascript的事件循环(event loop)_第2张图片

 

javascript的事件循环(event loop)_第3张图片

3、事件队列里分为:宏任务队列和微任务队列

1)、宏任务(macrotask )

宏任务一般包括:  setTimeout,setInterval,I/O 操作(包括AJAX请求),即上面的示例中setTimeout是宏任务。

 

2)、微任务(microtask )

微任务一般包括:promise.then() 里的操作

 

4、当宏任务碰到了微任务时

那么问题来了,当执行栈里的代码执行完毕,去事件队列里取任务时,先去微任务队列里的任务还是先去宏任务里的任务呢?

答:先取微任务里的任务。因为,微任务耗时少,快。

 

5、示例:当宏任务碰到了微任务时

console.log(1); //主线程

setTimeout(function(){
    console.log(2);//这个是宏任务,不管时间是多少(就算是0),都会进入到宏任务
},0);

new Promise(function(resolve,reject){
    resolve();//这个是then的回调函数,这个会进入到微任务
})
.then(function(){
    console.log(3);
});

console.log(4);//主线程



// 以上打印结果是:1,4,3,2

6、示例:当宏任务碰到了微任务时

多啰嗦一句,注意:在Promise的回调函数里的代码是同步,then里的回调才是异步的(resolve())

console.log(1); //主线程

setTimeout(function(){
    console.log(2);//这个是宏任务,不管时间是多少(就算是0),也会进入到宏任务
},0);

new Promise(function(resolve,reject){
    console.log(3); //Promise里的代码是同步的 (这行代码是新加的),所以,一开始在主线程执行
    resolve();//这个是then的回调函数,这个是进入到微任务
})
.then(function(){
    console.log(4);
});

console.log(5);

// 以上打印结果是:1,3,5,4,2

7、示例:Promise,async,await,setTimeout

如果不懂 async和await,请查看 什么是async,什么是await,async和await的区别,async和await的理解




    
    
    
    Document


    


 

 

你可能感兴趣的:(事件循环)