JS的事件循环
事件循环
关于事件循环,首先需要了解四个概念。
- 同步与异步
- 阻塞与非阻塞
1.同步与异步同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。换句话说,就是由调用者主动等待这个调用的结果。而异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。
2.阻塞与非阻塞阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态. 阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
然后JS是单线程异步非阻塞的。始终是非阻塞的(alert除外)。那么就存在一个问题,JS如何处理并行操作,毕竟单线程意味着它一次只能处理一件事情,答案是通过事件循环。
浏览器
如图可以看见堆,栈以及消息队列。重点关注执行栈和消息队列。
对于主线程的运行就应该像是这样:
首先执行被推入栈的函数,同时将这个已经被执行的函数(因为JS是异步非阻塞的,所以并不会认为得到了函数的返回才算是将函数执行完成)清理出栈。当执行栈变得干净了以后主线程得到空闲以后,就去执行消息队列,然后处理完消息队列以后继续检查栈。
这里的消息指的是被指定的异步操作完成以后应该执行的回调函数。
同时消息队列也分为两类。微任务(micro task)和宏任务(macro task)。微任务始终在宏任务之前执行。
以下事件属于宏任务:
- setInterval()
- setTimeout()
以下事件属于微任务
- new Promise()
- new MutaionObserver()
Node
对于Node,它引入了一个新的事件循环模型
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<──connections─── │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
- 外部输入数据
- 轮询阶段(poll)
- 检查阶段(check)
- 关闭事件回调阶段(close callback)
- 定时器检测阶段(timer)
- I/O事件回调阶段(I/O callbacks)
- 闲置阶段(idle, prepare)
- 轮询阶段...
这些阶段大致的功能如下:
- timers: 这个阶段执行定时器队列中的回调如 setTimeout() 和 setInterval()。
- I/O callbacks: 这个阶段执行几乎所有的回调。但是不包括close事件,定时器和setImmediate()的回调。
- idle, prepare: 这个阶段仅在内部使用,可以不必理会。
- poll: 等待新的I/O事件,node在一些特殊情况下会阻塞在这里。
- check: setImmediate()的回调会在这个阶段执行。
- close callbacks: 例如socket.on('close', ...)这种close事件的回调。