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线程继续接管,开始下一个宏任务(从事件队列中获取)

如图所示:

JS事件循环机制