Event Loop,你真的懂吗?

219 阅读4分钟

了解一些基础的概念。 堆,栈、队列

(Heap)是一种数据结构,是利用完全二叉树维护的一组数据。

(Stack)是只能表尾进行插入删除操作的线性表,它按照后进先出的原则存储数据

队列(Queue)是只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作的线性表。故队列又称为先进先出

JS执行栈

javascript是一门单线程语言,它有一个主线程(main thread)和 执行栈(call-stack) ,所有的任务都会被放到调用栈等待主线程执行。

既然是栈肯定是采用的后进先出的规则,当函数执行的时候,会被添加到栈的顶部,当执行栈执行完成后,就会从栈顶移出,直到栈内被清空。

例子:

就像我们平时排队买早餐,如果你只是需要已经做好的包子油条,店家可以马上给你,你拿着就走了。但如果你需要特制的炒粉等等,店家不能马上给你,需要现炒。那么店家会给你一个号码牌,让你到旁边等待,让后面排队的人跟上。

类比js的执行,买包子油条就像js中的同步任务,可以马上执行。买炒粉就像js中的异步任务需要被挂起。当粉炒好了,就想当于我们异步任务有返回,会被加入我们的事件队列。当我们执行栈被清空的时候,会读取事件队列中异步任务进主线程执行。

Event Loop

  • js 从上到下解析方法,将其中的同步任务按照执行顺序排列到执行栈中;
  • 当程序调用外部的 API 时(比如 ajax、setTimeout 等),会将此类异步任务挂起,继续执行执行栈中的任务。等异步任务返回结果后,再按照顺序排列到事件队列中;
  • 主线程先将执行栈中的同步任务清空,然后检查事件队列中是否有任务,如果有,就将第一个事件对应的回调推到执行栈中执行,若在执行过程中遇到异步任务,则继续将这个异步任务将这个异步任务挂起,等异步任务返回结果后,再按照顺序排列到事件队列中
  • 主线程每次将执行栈清空后,就去事件队列中检查是否有任务,如果有,就每次取出一个推到执行栈中执行,这个循环往复的过程被称为“Event Loop 事件循环”

宏任务、微任务

除了广义的同步任务和异步任务,我们对任务有更精细的定义,一种宏任务(MacroTask)也叫Task,一种叫微任务(MicroTask)

  1. macro-task(宏任务):当前调用栈中执行的任务称为宏任务。包含script全部代码、setTimeout、setInterval、setImmediate、I/O、UI Rendering。
  2. .micro-task(微任务): 当前(此次事件循环中)宏任务执行完,在下一个宏任务开始之前需要执行的任务为微任务。包含Process.nextTick(Node独有)、Promise、Object.observe(废弃)、MutationObserver
  3. 不同类型的任务会进入对应的Event Queue,宏任务中的事件放在callback queue中,由事件触发线程维护;微任务的事件放在微任务队列中,由js引擎线程维护。

不同类型的任务会进入对应的Event Queue,比如setTimeout和setInterval会进入相同的Event Queue。

setTimeout执行时机

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

setTimeout中的时间是等待加入事件队列中的时间,不是立即执行的时间。平时我们感觉它就是立即执行的时间,是因为当把setTimeout中的回调函数加入事件队列的时候执行栈就已经被清空了,所以它会立即执行。

setTimeout(fn,0),0秒后执行又是什么意思呢?是不是可以立即执行呢?

答案是不会的,setTimeout(fn,0)的含义是,指定setTimeout中的回调函数会被立即加入事件队列中,不用再等多少秒了,只要主线程执行栈内的同步任务全部执行完成,栈为空就马上执行。

测试一下

 setTimeout(function () {
      console.log('a');
      new Promise(function (resolve,reject) {
        resolve();
        console.log('b');
      }).then(function () {
        console.log('c')
      })
    },1000)
    setTimeout(function () {
      console.log('d');
    },0)
    new Promise(function (resolve,reject) {
      reject();
      console.log('e');
    }).then(function () {
      console.log('f')
    }).catch(()=>{
      console.log('g')
    })
    console.log('h')
    
    
    
    
    //e h g d a b c