详解高级前端面试常问的EventLoop

6,677 阅读4分钟

首先啥是EventLoop?

整个js这种运行机制又称为Event Loop(事件循环)

EventLoop又分为浏览器端和node端两种

上代码感受一下

 setTimeout(() => {
        console.log(1)
        Promise.resolve(3).then(data => console.log(data))
      }, 0)
      
setTimeout(() => {
        console.log(2)
      }, 0)
//浏览器 1 3 2
//node 1 2 3

浏览器的EventLoop

首先就是执行时会有一个栈 然后有一个事件队列和微任务空间(自己命名的)

当我们的代码从上到下同步执行时,遇到setTimeout就记时,当时间到时就把此事件放到事件队列中,遇到微任务就把微任务放到微任务空间,代码会继续向下执行,直到同步代码执行完毕。

完毕后,会看看微任务空间中有没有微任务,有就把微任务空间中的微任务全部执行,然后去队列中取我们的事件执行,执行时若有微任务继续放到微任务空间,当此事件执行完毕,还会把微任务空间中的微任务全部执行完毕,然后再去取队列中的异步任务。。。。反复循环

有图有真相(是不是很丑!丑也别说出来)

微任务和宏任务

  • 微任务 Promise的then, (MutationObserver)
  • 宏任务 setInterval, setTimeout, setImmediate(ie), MessageChannel

node端的EventLoop

想上个丑图

再来段文字描述 争取把自己绕晕

  • setTimeout 和 setImmediate 执行顺序不固定 取决于node的准备时间

setImmediate 设计在poll阶段完成时执行,即check阶段; setTimeout 设计在poll阶段为空闲时,且设定时间到达后执行;但其在timer阶段执行 其二者的调用顺序取决于当前event loop的上下文,如果他们在异步i/o callback之外调用(在i/o内调用因为下一阶段为check阶段),其执行先后顺序是不确定的,需要看loop的执行前的耗时情况

  • 上图每一个方框都对应一个事件队列,当event loop执行到某个阶段时会将当前阶段对应的队列依次执行。当队列执行完毕或者执行的数量超过上线时,会转入下一个阶段
  • 微任务是在切换对列是执行

废话不多说,上代码

setImmediate(() => {
  console.log('setImmediate1')  
  setTimeout(() => {
    console.log('setTimeout1')
  }, 0);
})
setTimeout(()=>{
  console.log('setTimeout2')
  setImmediate(()=>{
    console.log('setImmediate2')
  })
},0);
//首先setImmediate和setTimeout执行顺序不固定 
//所以setImmediate1和setTimeout2,打印顺序不定
//假设 setImmediate1先打印   有两种顺序
//1.执行顺序是 setImmediate1  setTimeout2  setTimeout1 setImmediate2
//2.执行顺序是 setImmediate1  setTimeout2  setImmediate2 setTimeout1
// 1.setImmediate1打印后把setTimeout1放进timer队列 然后执行setTimeout2 这时候把setImmediate2放进check队列中  打印setTimeout2 发现timer中还有setTimeout1 他就会把timer对列中的执行完 才会执行下一队列的代码
//然后打印setTimeout1, 切换对列到check  打印setImmediate2
//2. setImmediate1打印后把setTimeout1放进timer队列 然后执行setTimeout2 这时候把setImmediate2放进check队列中  打印setTimeout2 这个时候node执行过快setTimeout1还没有到时间,所以切换对列执行setImmediate2 然后是setTimeout1
//setTimeout第二个参数虽然是0,但是都有默认时间一般是4ms左右

再来一题

setImmediate(() => {
  console.log('setImmediate1')  
  setTimeout(() => {
    console.log('setTimeout1')
  }, 0);
})
setTimeout(()=>{
  console.log('setTimeout2')
  process.nextTick(()=>console.log('nextTick'))
  setImmediate(()=>{
    console.log('setImmediate2')
  })
},0);
//假设setImmediate1先
//setImmediate1  setTimeout2 setTimeout1  nextTick setImmediate2
//setImmediate1  setTimeout2 nextTick setImmediate2 setTimeout1
//微任务会在切换对列时执行  上面从timer对列切换到check对列时nextTick执行
//上面一题明白了这一个也明白了

再来一题

let fs = require('fs');
fs.readFile('./1.txt',function () {
  console.log(1);
  setTimeout(() => {
    console.log('setTimeout')
  }, 0);
  setImmediate(() => {
    console.log('setImmediate')
  });
});

//顺序固定 1 setImmediate setTimeout
// 为什么呢?看上面的图  首先readFile肯定是poll对列   执行时把setTimeout setImmediate放到各自队列 然后按上下顺序查找对列  查找到check队列发现有 就把setImmediate打印出  然后顺序检查下一队列close没有 循环 再次从上往下找  直到timer打印setTimeout

以上内容是本人试出来的 mac vscode

微任务有两种 nextTick和then 那么这两个谁快呢?

 Promise.resolve('123').then(res=>{
   console.log(res);
   
 })
  process.nextTick(() => console.log('nextTick'))
  
  //顺序  nextTick 123
  //很明显  nextTick快