JS 引擎是单线程的,直白来说就是一个时间点下 JS 引擎只能去做一件事情,而 Java 这种多线程语言,可以同时做几件事情。

JS 做的任务分为同步和异步两种,所谓 “异步”,简单说就是一个任务不是连续完成的,先执行第一段,等做好了准备,再回过头执行第二段,第二段也被叫做回调;同步则是连贯完成的。

像读取文件、网络请求这种任务属于异步任务:花费时间很长,但中间的操作不需要 JS 引擎自己完成,它只用等别人准备好了,把数据给他,他再继续执行回调部分。

如果没有特殊处理,JS 引擎在执行异步任务时,应该是存在等待的,不去做任何其他事情。这样在执行异步任务时有大量的空闲时间被浪费。

实际上这确实是大多数多线程语言的处理办法。但对于 JS 这种单线程语言来说,这种长时间的空闲等待是不可接受的:遇到其他紧急任务,Java 可以再开一个线程去处理,JS 却只能干等。

所以 JS 采取了“异步任务回调通知”模式:

在等待异步任务准备的同时,JS 引擎去执行其他同步任务,等到异步任务准备好了,再去执行回调。这种模式的优势显而易见,完成相同的任务,花费的时间大大减少,这种方式也被叫做非阻塞式。

而实现这个“通知”的,正是事件循环,把异步任务的回调部分交给事件循环,等时机合适交还给 JS 线程执行。事件循环并不是 JavaScript 首创的,它是计算机的一种运行机制。

事件循环是由一个队列组成的,异步任务的回调遵循先进先出,在 JS 引擎空闲时会一轮一轮地被取出,所以被叫做循环。

根据队列中任务的不同,分为宏任务和微任务。

JS最主要的两个运行环境就是:浏览器 和 Node,我们接下来也会基于这两个运行环境,对JS的运行机制进行讲解

浏览器事件循环

浏览器的事件循环由一个宏任务队列+多个微任务队列组成。

首先,执行第一个宏任务:全局 Script 脚本。产生的的宏任务和微任务进入各自的队列中。执行完 Script 后,把当前的微任务队列清空。完成一次事件循环。

接着再取出一个宏任务,同样把在此期间产生的回调入队。再把当前的微任务队列清空。以此往复。

宏任务队列只有一个,而每一个宏任务都有一个自己的微任务队列,每轮循环都是由一个宏任务+多个微任务组成。

  1. Promise.resolve().then(()=>{
  2. console.log('第一个回调函数:微任务1')
  3. setTimeout(()=>{
  4. console.log('第三个回调函数:宏任务2')
  5. },0)
  6. })
  7. setTimeout(()=>{
  8. console.log('第二个回调函数:宏任务1')
  9. Promise.resolve().then(()=>{
  10. console.log('第四个回调函数:微任务2')
  11. })
  12. },0)
  13. // 第一个回调函数:微任务1
  14. // 第二个回调函数:宏任务1
  15. // 第四个回调函数:微任务2
  16. // 第三个回调函数:宏任务2

调用栈

  1. console.log('1')
  2. setTimeout(function callback(){
  3. console.log('2')
  4. }, 1000)
  5. new Promise((resolve, reject) => {
  6. console.log('3')
  7. resolve()
  8. })
  9. .then(res => {
  10. console.log('4');
  11. })
  12. console.log('5')

macrotask(宏任务microtask(微任务

事件循环由宏任务和在执行宏任务期间产生的所有微任务组成。完成当下的宏任务后,会立刻执行所有在此期间入队的微任务。

这种设计是为了给紧急任务一个插队的机会,否则新入队的任务永远被放在队尾。区分了微任务和宏任务后,本轮循环中的微任务实际上就是在插队,这样微任务中所做的状态修改,在下一轮事件循环中也能得到同步。

宏任务包括:script(整体代码), setTimeout, setInterval, requestAnimationFrame, I/O,setImmediate

其中setImmediate只存在于Node中,requestAnimationFrame只存在于浏览器中。

微任务包括: Promise, Object.observe(已废弃), MutationObserver(html5新特性),process.nextTick

其中process.nextTick只存在于Node中,MutationObserver只存在于浏览器中。

注意:
UI Rendering不属于宏任务,也不属于微任务,它是一个与微任务平行的一个操作步骤。 HTML规范文档

这种分类的执行方式就是,执行一个宏任务,过程中遇到微任务时,将其放到微任务的事件队列里,当前宏任务执行完成后,会查看微任务的事件队列,依次执行里面的微任务。如果还有宏任务的话,再重新开启宏任务……


Node 事件循环

Node 事件循环