面试必问的事件机制event loop,你会吗?

835 阅读5分钟

前端面试常常会有这样的问题困扰你,哪个先输出,哪个后输出。。。

Promise.resolve('123').then(data=>{
    console.log(1);
})
process.nextTick(function() {
    console.log(2)
})
setImmediate(function(){
    console.log(3)
})
setTimeout(function(){
    console.log(4)
})
setTimeout(()=>{
    console.log('setTime1');
    Promise.resolve('123').then(data=>{
        console.log('p');
    })
})
setImmediate(()=>{
    console.log('setImmediate1')
    setTimeout(()=>{
        console.log('setTimeout1')
    })
})
setTimeout(()=>{
    console.log('setTimeout2')
    process.nextTick(()=>{console.log('nextick')})
    setImmediate(()=>{
    console.log('setImmediate2')
        
    })
})
  • 说实话不了解事件环的时候我都是蒙逼了,这都什么鬼。。。
  • 看完这一篇,解决你所有困扰。

1.js单线程

  • 谈到事件环,必须要说的就是进程和线程,进程是操作系统分配资源和调度任务的基本单位,线程是建立在进程上的一次程序运行单位,一个进程上可以有多个线程。打开任务管理器,我们会发现,每一个起点任务都是进程,会占用内存,cpu,所以说每个网页都是一个单独的进程,即使自己挂掉,也不会影响其他页面的进程。

1.1 单线程

  • 常常会听别人说js是单线程的,实际上js的主线程是单线程的,比如:不能同时操作一个DOM。

1.2 js其他线程

  • 实际上js也有其他线程,比如:子线程,比如: 异步线程 (setTimeout,浏览器事件,ajax回调函数),那什么是同步和异步呢?

1.3 同步和异步

  • 同步就是发出调用后,没有得到结果之前,该调用不返回,一旦调用返回,就得到返回值了。就是调用者主动等待这个调用的结果。
  • 异步就是调用者在发出调用后这个调用就直接返回了,所以没有返回结果。不会立刻得到结果,而是调用发出后,被调用者通过状态、通知或回调函数处理这个调用。
  • 而通常js里面的异步基本上包括定时器,ajax,promise,回调等,我们又把这些异步分为了宏任务和微任务。

1.4 宏任务和微任务

  • 谈到宏任务和微任务,分为浏览器环境下的和node环境下的。

1.4.1 浏览器环境下

  • macro-task(宏任务): setTimeout, setInterval, setImmediate(ie浏览器特有),MessageChannel
  • micro-task(微任务): Promise.then,Object.observe(已废弃), MutationObserver

1.4.2 node环境下

  • macro-task(宏任务): setTimeout, setInterval, setImmediate, I/O
  • micro-task(微任务): process.nextTick,Promise.then,Object.observe(已废弃), MutationObserver(vue中使用但由于不兼容放弃)

1.5 队列和栈

  • 队列Queue 先进先出,就像管道一样,一头进去另一头出来,如图:
  • 栈Stack先进后出,也就是后进先出,如图:
    场景就是我们是函数作用域,函数放进去栈的顺序是one -> rwo -> three,但是执行顺序是three -> two -> one
function one() {
    return function two(){
        return function three(){
            ...
        }
    }
}
  • 那在这里说队列跟栈有什么关系呢?主要是为了说明js执行过程中,同步执行的代码是要放在栈内执行的,而异步代码是要放在队列中等待后,拿到栈里执行的,什么意思呢?一言不合就上图
  • js执行过程中是在栈中执行(也就是我们所说的主线程),主线程之外,还存在一个任务队列;
  • 只要异步任务有了运行结果,就在任务队列之中放置一个事件。一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,将队列中的事件一个一个打放到执行栈中依次执行;
  • 如果队列中的事件有微任务,会在执行完栈中后,然后执行微任务队列,再去宏任务队列中去异步任务,这个过程是循环不断的。执行顺序 主线程=>microtask=>macrotask
  • 举例说明一下

2.浏览器机制

console.log('start');
Promise.resolve('123').then(data=>{
    console.log('Promise1');
});
setTimeout(()=>{
    console.log('setTimeout1');
    Promise.resolve('123').then(data=>{
        console.log('Promise2');
    })
});
setImmediate(()=>{
    console.log('setImmediate1');
    setTimeout(()=>{
        console.log('setTimeout2');
    });
});
setTimeout(()=>{
    console.log('setTimeout3');
    setImmediate(()=>{
        console.log('setImmediate2');
    });
});
console.log('end');
  1. 首先会执行栈中的,输出:startend
  2. 然后要去执行微任务队列,输出:promise1
  3. 执行完毕后,一项一项地读取宏任务,而在chrome浏览器中setImmediate默认没有等待时间是要比默认等待时间为4ms的setTimeout率先放入队列中的。
  4. 此时队列的顺序应该是setImmediate1->setTimeout1->setTimeout3根据队列先进先出,所以此时首先输出:setImmediate1,setImmediate1中的setTimeout2这个定时器会在4ms后放入队列,
  5. 然后去队列中取下一项setTimeout1执行,输出setTimeout1,此时setTimeout1这个定时器中含有微任务promise.then,会在setTimeout1执行完后马上执行,此时输出Promise1
  6. 再去队列中取出setTimeout3放到栈中执行,到此为止如果它里面的setImmediate2如果此时4中的setTimeout2已经先放入队列中了,说明首先setTimeout2会被取出,放于栈中执行,如果此时setTimeout2尚未放入队列中,那说明setImmidiate2会被先取出,放于栈中执行。
start//首先执行栈中
end//首先执行栈中
Promise1//promise.then是微任务
setImmediate1
setTimeout1
Promise2
setTimeout3
setImmediate2//最后两个位置不能确定看执行速度而确定
setTimeout2

3. node运行机制

  • 根据1.4.2会得知node的宏任务以及微任务。
  • 那么相同的情况下,node中运行机制如何呢?与浏览器环境有何不同呢?一言不合就上图
  • timers 阶段: 这个阶段执行setTimeout以及 setInterval的callback;
  • poll 阶段: 执行poll中的I/O, 检查定时器是否到时
  • check 阶段: 执行setImmediate() 设定的callback;
  • close callbacks 阶段: 比如socket.on(‘close’)的callback会在这个阶段执行。
  • 值得注意到是
  1. 每一种任务会放进各自的队列中,只有当本队列全部执行完毕,才会走到下一个队列中;
  2. 每次跳队列的过程中会走到微任务队列中执行,执行完毕才会按照顺序队列往下走,每个队列执行顺序是固定的;
  3. 对于同是微任务的process.nextTick要快于promise.then;
  4. 简单理解整个过程 timer->microtask->I/O->microtask->setImmidate->microtask
  5. 而对于setTimeoutsetImmediate来说,由node运行速度决定,node如果<4ms开启运行,那么setImmediate要优先setTimeout放入队列中。
  6. poll阶段,不仅执行I/O操作,同时还会检查定时器是否到时,如果此时timer到时间,会去执行timer quene而不会继续往下走,所以情况很多,并且特别烧脑。

谢谢你看到了最后