深入浅出JS—22事件循环及面试题

1. JS中为什么要有事件循环

由于JS是单线程的,一些异步问题通常交由别的线程来处理,处理之后将结果加入事件队列,当JS线程代码执行完,会去事件队列中取事件,到JS线程继续执行,直到事件队列被清空:
JS线程——》其他线程——》事件队列
这三者构成了闭环,形成了事件循环

2. 浏览器中的事件循环

深入浅出JS—22事件循环及面试题_第1张图片

任务队列:

  • 宏任务队列:setTimeout; setInterval; ajax; dom事件点击等
  • 微任务队列:Promise.then; queueMicrotask

优先级:

  • 微任务队列>宏任务队列
  • 同一个队列内部:先进先出
    注意:async函数会像普通函数一样正常执行,如果函数里面有await关键字,那么await后面表达式返回的是一个Promise对象,Promise.then是微任务

题目1

async-await promise.then queueMicrotask setTimeout

async function async1 () {
  console.log('async1 start')
  await async2(); 
  console.log('async1 end');
}

async function async2 () {
  console.log('async2')
}

console.log('script start')

setTimeout(function () {
  console.log('setTimeout2')
}, 10) 

setTimeout(function () {
  console.log('setTimeout1')
}, 0)
 
async1();
 
new Promise (function (resolve) {
  console.log('promise1')
  resolve();
}).then (function () {
  console.log('promise2')
})

queueMicrotask(()=>{ console.log('queueMicrotask') })

console.log('script end')

/**
 * script start 
 * async1 start
 * async2
 * promise1
 * script end
 * async1 end
 * promise2
 * queueMicrotask
 * setTimeout1
 * setTimeout1
 */
  • await后面表达式属于上一个代码块,表达式内容会被立即执行,返回promise.then会加入微任务队列
  • setTimeout( ()=>{},10):回调函数10ms后才被加入宏任务队列

题目2

new Promise((resolve) => {
  resolve();
})
  .then(() => {
    new Promise((resolve) => {
      console.log(000);
      resolve();
    })
      .then(() => {
        console.log(111);
      })
      .then(() => {
        console.log(222);
      });
    // return undefined;
  })
  .then(() => {
    console.log(333);
  });
  
/**
 * 000
 * 111
 * 333
 * 222
 */
  • 执行完console.log(000)之后,就将之后的then函数塞入微任务队列
  • 然后执行return undefined;
  • 然后将console.log(333)的then塞入微任务队列
  • 这时取出console.log(111)执行完后,将console.log(222)加入任务队列的队尾巴
  • 所以333在222之前输出

3. Node中的事件循环

浏览器中的事件循环是根据HTML5定义的规范来实现的,不同的浏览器可能会有不同的实现,而Node中是由libuv实现的,libuv是一个多平台的专注于异步IO的库,主要维护了一个EventLoop和worker threads(线程池)
深入浅出JS—22事件循环及面试题_第2张图片
事件循环像是一个桥梁,是连接着应用程序的JavaScript和系统调用之间的通道

  • 无论是文件IO、数据库、网络IO、定时器、子进程,在完成对应的操作后,都会将对应的结果和回调函数放到事件循环(任务队列)中;
  • 事件循环会不断的从任务队列中取出对应的事件(回调函数)来执行;

Node中的事件循环

  1. 定时器(Timers):执行setTimeout() 和 setInterval() 的回调函数。
  2. 待定回调(Pending Callback):对某些系统操作(如TCP错误类型)执行回调
  3. idle, prepare:仅系统内部使用。
  4. 轮询(Poll):检索新的 I/O 事件;执行与 I/O 相关的回调;
  5. 检测(check):setImmediate() 回调函数在这里执行。
  6. 关闭的回调函数:一些关闭的回调函数,如:socket.on(‘close’, …)

Node中任务队列

微任务队列

  1. next tick queue:process.nextTick;
  2. other queue:Promise的then回调、queueMicrotask;

宏任务队列:

  1. timer queue:setTimeout、setInterval;
  2. poll queue:IO事件;
  3. check queue:setImmediate;
  4. close queue:close事件

优先级:

  • 微任务>宏任务
  • 微任务中:process.nextTick队列 > 其他队列
  • 宏任务中:setTimeout,setInterval队列 > IO队列 >setImmediate队列>close队列
  • 每个队列中,先进先出

与浏览器区别

  • 多了方法: procress.nextTick, setImmediate
  • 微任务中有两种队列,宏任务中有四种队列,注意队列的优先级关系
  • Node中事件循环,不仅包括宏任务,还有一些其他pending callbak和idle,prepare等

题目1

async function async1() {
  console.log('async1 start')
  await async2()
  console.log('async1 end')
}

async function async2() {
  console.log('async2')
}

console.log('script start')

// 300ms后加入事件队列
setTimeout(function () {
  console.log('setTimeout2')
}, 300)

setImmediate(() => console.log('setImmediate'));

setTimeout(function () {
  console.log('setTimeout0')
}, 0)

process.nextTick(() => console.log('nextTick1'));

async1();

process.nextTick(() => console.log('nextTick2'));

new Promise(function (resolve) {
  console.log('promise1')
  resolve();
  console.log('promise2')
}).then(function () {
  console.log('promise3')
})

console.log('script end')

/**
 * script start
 * async1 start
 * async2
 * promise1
 * promise2
 * script end
 * nextTick1
 * nextTick2
 * async1 end
 * promise3
 * setTimeout0
 * setImmediate
 * setTimeout2
 */
  • process.nextTick是先于promise.then执行
  • resolve()将then的回调函数加入微任务队列中,之后的语句console.log(‘promise2’)依然会立即执行
  • setTimeout队列的优先级高于setImmediate,如果前者队列中有值,会先清空

你可能感兴趣的:(深入理解JavaScript,javascript,前端,node.js,面试)