事件循环
在浏览器端,JS
是单线程的,也就是说,在同一个时刻最多只有一个代码片段在执行,可是浏览器又可以很好的处理异步请求,到底是为什么呢?
先来说明执行中的两个线程:
- 主线程:
JS
引擎执行的线程,只有一个,负责页面渲染、函数处理。 - 工作线程:也称为幕后线程,这个线程可能存在于浏览器或
JS
引擎内部,与主线程是分开的,处理文件读取、网络请求等异步事件。
在主线程中有一个执行栈,所有的 JS
代码都会在执行栈里运行。在执行代码的过程中,如果遇到一些异步代码,比如,setTimeout
,ajax
等等,那么浏览器就会将这些代码放到工作线程中执行,在前端由浏览器底层执行,这个线程的执行不会阻塞主线程的执行,主线程继续执行栈中的剩余代码。
当工作线程里的代码执行完成后,该线程就会将它的回调函数放到任务队列中(又称为事件队列,消息队列)等待执行。而当主线程执行完栈中的所有代码后,它就会检查任务队列是否有任务要执行,如果有任务要执行的话,那么就将该任务放到执行栈中执行。如果当前任务队列为空的话,它就会一直循环等待任务的到来,因此,这也被称为事件循环。
任务队列
从上面可知,工作线程会将异步的回调函数放到任务队列中,然后让主线程来执行,那么问题来了,如果任务队列中有多个任务,那么要执行哪个呢?
在 JS
中,有两个任务队列,一个叫做 Macrotask Queue(Task Queue)
大任务,另一个是 Microtask Queue
小任务。
Macrotask
常见的任务:
setTimeout
setInterval
setImmediate
I/O
- 用户交互操作,
UI
渲染
Microtask
常见的任务:
Promise
process.nextTick
Object.observe
如果两种任务同时出现,事件循环执行是这样的:
- 检查大任务队列是否为空,若不为空,则进行下一步,若为空,跳到3
- 从大任务队列中取队首(在队列时间最长)的任务进去执行栈中执行(仅仅一个),执行完进入下一步
- 检查小任务队列是否为空,若不为空,则进行下一步,否则跳到1
- 从小任务队列中取出队首(在队列时间最长)的任务进去事件队列
简而言之,一次事件循环只执行处于 Macrotask
队首的任务,执行完成后,立即执行 Microtask
队列中的所有任务。
基于这个结论来看一个例子:
console.log(1)
setTimeout(function