js事件循环

305 阅读2分钟

写在前面

事件循环即EventLoop,说到EventLoop就必须先了解几个概念:单线程、stack、queue、webapi、Macrotask、Microtask。

单线程

众所周知,js是单线程语言,即同一个时间只能做一件事,如果处理一件事情需要很久的话,后面的事情就会被阻塞。

为什么js是单线程语言呢?

这与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

相关概念

  • stack(执行栈):执行js代码的地方,先进后出。
  • queue(回调队列):异步任务中可执行代码的等待区域,先进先出。
  • webapis: 维护异步任务的地方,直接点说就是主线程之外的其他线程,这种说法可能不够准确。
  • Microtask(微任务):回调队列的一种,执行栈空出来的时候优先取Microtask队列里的代码执行。
  • Macrotask(宏任务):回调队列的一种,优先级低于Microtask。

EventLoop 具体流程

举一个例子具体说下EventLoop的流程:

console.log(1);
setTimeout(function(){
	console.log(2)
},0);
console.log(3)
//输出结果是1,3,2
  1. console.log(1)被压入执行栈。
  2. setTimeout在执行栈被识别为异步任务,放入webapis中。
  3. console.log(3)被压入执行栈,此时setTimeout的可执行代码还在回调队列里等待。
  4. console.log(3)执行完成后,从回调队列头部取出console.log(2),放入执行栈。
  5. console.log(2)执行。

再举一个例子说下Microtask和Macrotask:

console.log(1);
setTimeout(()=>{
	console.log(2)
})
var p = new Promise((resolve,reject)=>{
	console.log(3)
	resolve("成功")
})
p.then(()=>{
	console.log(4)
})
console.log(5)
//按照上个例子的理解输出结果应该为:1、3、5、2、4,但实际上输出的结果为:1、3、5、4、2

总结

  • 第二个例子的输出结果为什么是:1、3、5、4、2呢?前面Microtask概念里提到过执行栈空出来的时候优先取Microtask队列里的代码执行。难道Promise的then是微任务!? 是的,then是微任务。
  • 常见的微任务有哪些:process.nextTick、Promise、Object.observe、MutationObserver,优先级也是按照这个顺序。
  • 常见的宏任务:setTimeout/setInterval、事件、ajax等。
  • 盗张图来看下EventLoop:

参考链接