解剖事件环

167 阅读3分钟
解剖事件环
  1. js是单线程,是指主线程。但主线程之内还有很多非单线程的内容,比如定时器,比如promise,比如http请求。
  2. 在浏览器事件环机制中,当执行栈的同步代码清空后,系统会去读取任务队列。什么叫执行栈的同步代码清空?其实就是主线程之内能一步过的代码运行完毕。那么什么叫任务队列?其实就是那些非单线程的内容,js执行的时候,会把这些异步的内容先保存起来,统一放到一个队列里,等待主线程里的同步代码运行完毕后再来处理这些任务,所以叫任务队列。
  3. 所谓任务队列,其实也是有分别的。这里面分两大类:宏任务和微任务。常见的宏任务包括setTimeout,setImmediate(只有ie支持),setInterval,messageChannel。常见的微任务则包括Promise.then(),mutationObserver。
  4. 既然任务队列有分别,那么处理这些任务的时候就有了优先级————优先处理微任务。把微任务清空后(如果处理过程中有新的微任务,在处理当前微任务后会马上处理新的微任务,并在下次执行宏任务前全部清空),再依次读取宏任务,这里特别注意,并非一次性执行完所有宏任务,而是像队列那样,先取一个宏任务执行,执行完后,再去看是否有微任务,如果有,则执行微任务,然后再读取一个宏任务执行,不断循环。
  5. node.js中的事件环跟浏览器里的事件环机制又有区别:在nodejs的事件环机制中,是优先执行微任务,但是当执行完微任务,进入宏任务的时候,即使在执行宏任务过程中存在新的微任务,也不会优先执行微任务,而是把宏任务队列中执行完毕。
  6. async函数立即执行。
  7. awaite函数里的表达式立即执行,await后面的代码则相当于向微任务队列里添加了微任务,await的作用是让出一个线程。
  8. 例子:
setTimeout(() => {
  console.log('我是setTimeout1')
  Promise.resolve().then(()=>{
    console.log('我是Promise1')
  })
}, 0);

Promise.resolve().then(()=>{
  console.log('我是Promise2')
  setTimeout(() => {
    console.log('我是setTimeout2')
  }, 0);
  Promise.resolve().then(()=>{
    console.log('我是Promise3')
  })
  console.log('我是新插进来的')
})

console.log("我是最后一个")
打印顺序为:
'我是最后一个' -> '我是Promise2' -> '我是新插进来的' ->  '我是Promise3' -> '我是setTimeout1' -> '我是Promise1' -> '我是setTimeout2'
解析:
  1. js执行,碰到setTimeout,丢到任务队列里(宏任务),碰到promise,丢到任务队列里(微任务);先执行完主线程里的单线程,也就是执行栈里的同步代码,输出“我是最后一个”。
  2. 开始处理任务队列。
  3. 优先处理微任务,打印'我是Promise2',碰到setTimeout,继续丢到任务队列,向下执行,输出'我是Promise3'。
  4. 微任务处理完毕,去看看任务队列里有没有宏任务。
  5. 处理先进去的那个setTimeout,输出'我是setTimeout1',向下又发现了一个微任务Promise,丢到任务队列。
  6. 好的,一个宏任务已经完成了,去看看有没有微任务,发现了刚丢进去的promise,处理一下,输出'我是Promise1'。
  7. 微任务全处理了,去看看有没有微任务,发现了最后丢进去的那个碰到setTimeout,进行处理,输出'我是setTimeout2'。至此全部输出完毕。