JS事件循环机制
区分进程和线程
- 进程是cpu资源分配的最小单位(系统会给它分配内存)
- 线程是cpu调度的最小单位,一个进程中可以并发多个线程
- 不同进程之间是可以相互通信的
浏览器的渲染进程是多线程的
1. GUI渲染线程
- 负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行注意,GUI渲染线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起(相当于被冻结了),GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。
2. JS引擎线程
- 也称为JS内核,负责处理Javascript脚本程序。(例如:V8引擎)JS引擎线程负责解析Javascript脚本,运行代码。 JS引擎一直等待着任务队列中任务的到来,然后加以处理,一个Tab页(renderer进程)中无论什么时候都只有一个JS线程在运行JS程序。同样注意,GUI渲染线程与JS引擎线程是互斥的,所以如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞。
3. 事件触发线程
-
归属于浏览器而不是JS引擎,用来控制事件循环(可以理解,JS引擎自己都忙不过来,需要浏览器另开线程协助)当JS引擎执行代码块如setTimeOut时(也可来自浏览器内核的其他线程,如鼠标点击、AJAX异步请求等),会将对应任务添加到事件线程中。当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理。注意,由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)
4. 定时触发器线程
- setInterval与setTimeout所在线程,浏览器定时计数器并不是由JavaScript引擎计数的,(因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确), 因此通过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待JS引擎空闲后执行)注意,W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms。
5. 异步HTTP请求线程
-
在XMLHttpRequest在连接后是通过浏览器新开一个线程请求,将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中。再由JavaScript引擎执行。
事件循环机制
1. 宏任务(macrotask)
- 说明:每次执行栈执行代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)
- 宏任务包含: script(主代码)、setTimeout、setInterval、I/O、UI rendering、MessageChannel(浏览器)、requestAnimationFrame(浏览器)、setImmedidate(Node.js 环境)
- 执行顺序:主代码块 > I/O > UI渲染 > requestAnimationFrame
MessageChannel > setTimeout/setInterval
2. 微任务(microtask)
- 说明: 可以理解是在当前
task
执行结束后立即执行的任务 - 微任务包含:Promise.then、Object observe、MutationObserver、process.nextTick(Node.js 环境)、async(基于promise实现)
- 执行顺序: process.nextTick > Promise > MutationObserver
运行机制:
- 执行一个宏任务(栈中没有就从事件队列中获取)
- 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
- 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
- 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
- 渲染完毕后,
JS
线程继续接管,开始下一个宏任务(从事件队列中获取)
如图所示: