浏览器和nodejs中的事件循环

203 阅读3分钟

1、参考文章:

JS事件循环

浏览器/nodeJS中的EventLoop

2、为什么JavaScript是单线程的?

单线程意思就是说同一个时间只能做一件事。那这样的话效率不是很低?也没有啦,其实javascript的单线程特点是跟他的用途有关的。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。假如不是单线程的话,在一个线程当我们在给某个DOM节点增加内容的时候,另一个线程正在删除这个DOM节点的内容,那还得了,那不是乱套了吗。所以javascript只能是单线程。
虽然javascript是单线程,但是javascript中有同步和异步的概念,解决了js阻塞的问题。avascript就可以进行同步任务和异步任务。把读文件这种操作,ajax请求这些需要耗时的任务放到任务队列中,我还是能够一步步的继续下面的任务。

3、js中的事件循环(Event Loop)

Event Loop是指在js执行环境中存在主执行线程和任务队列(Task Queue),其中所有同步任务都在主执行线程中形成一个执行栈,所有异步任务都会放到任务队列中。Event Loop会经历如下过程:

  • 主线程执行同步任务,在主线程执行过程中,不断形成堆栈并执行出栈入栈的操作
  • 主线程任务是否执行完毕,如否,继续循环第1步,如是,则执行下一步
  • 系统读取任务队列里的任务,进入执行栈,开始执行
  • 不断循环执行前三步

4、浏览器环境

异步任务分为task(宏任务,也可称为macroTask)和microtask(微任务)两类。 当满足执行条件时,task和microtask会被放入各自的队列中等待放入主线程执行,我们把这两个队列称为Task Queue(也叫Macrotask Queue)和Microtask Queue。

  • task:script中代码、setTimeout、setInterval、I/O、UI render。
  • microtask: promise、Object.observe、MutationObserver。

执行过程:

  1. 执行完主执行线程中的任务。
  2. 取出Microtask Queue中任务执行直到清空。
  3. 取出Macrotask Queue中一个任务执行。
  4. 取出Microtask Queue中任务执行直到清空。
  5. 重复3和4。

即为同步完成,一个宏任务,所有微任务,一个宏任务,所有微任务......

5、node环境

各个阶段执行的任务如下:

  • timers 阶段: 这个阶段执行setTimeoutsetInterval预定的callback;
  • I/O callbacks 阶段: 执行除了 close事件的callbacks、被timers设定的callbackssetImmediate()设定的callbacks这些之外的callbacks;
  • idle, prepare 阶段: 仅node内部使用;
  • poll 阶段: 获取新的I/O事件, 适当的条件下node将阻塞在这里;
  • check 阶段: 执行setImmediate() 设定的callbacks;
  • close callbacks 阶段: 执行socket.on('close', ...)这些 callback

6、测试代码

function sleep(time) {
  let startTime = new Date()
  while (new Date() - startTime < time) {}
  console.log('1s over')
}
setTimeout(() => {
  console.log('setTimeout - 1')
  setTimeout(() => {
      console.log('setTimeout - 1 - 1')
      sleep(1000)
  })
  new Promise(resolve => resolve()).then(() => {
      console.log('setTimeout - 1 - then')
      new Promise(resolve => resolve()).then(() => {
          console.log('setTimeout - 1 - then - then')
      })
  })
  sleep(1000)
})

setTimeout(() => {
  console.log('setTimeout - 2')
  setTimeout(() => {
      console.log('setTimeout - 2 - 1')
      sleep(1000)
  })
  new Promise(resolve => resolve()).then(() => {
      console.log('setTimeout - 2 - then')
      new Promise(resolve => resolve()).then(() => {
          console.log('setTimeout - 2 - then - then')
      })
  })
  sleep(1000)
})

  • 浏览器输出

setTimeout - 1 //1为单个task
1s over
setTimeout - 1 - then
setTimeout - 1 - then - then 
setTimeout - 2 //2为单个task
1s over
setTimeout - 2 - then
setTimeout - 2 - then - then
setTimeout - 1 - 1
1s over
setTimeout - 2 - 1
1s over
  • node输出

setTimeout - 1 
1s over
setTimeout - 2 //1、2为单阶段task
1s over
setTimeout - 1 - then
setTimeout - 2 - then
setTimeout - 1 - then - then
setTimeout - 2 - then - then
setTimeout - 1 - 1
1s over
setTimeout - 2 - 1
1s over