每个渲染进程都有一个主线程,并且主线程非常繁忙,既要处理DOM,又要计算样式,还要处理布局,同时还需要处理JavaScript任务以及各种输入事件。为了让这些不同类型的任务在主线程中有条不紊的执行,就需要一个系统来统筹调度这些任务,这个统筹调度系统就是消息队列和事件循环系统。
第一版:使用单线程处理安排好的工作
线程的一次执行
第二版:在线程中引入事件循环
原因:并不是所有的任务都是在执行之前统一安排好的,大部分情况下,新的任务是在线程运行过程中产生的。
解决:要想在线程运行过程中,能接收并执行新的任务,就需要采用事件循环机制。即:
- 引入了循环机制,具体实现方法是在线程语句最后添加了一个for循环语句,线程会一直循环执行。
- 引入了事件,可以在线程运行过程中,等待用户输入的数字,等待过程中线程处于暂停状态,一旦接收到用户输入的信息,那么线程会被激活,然后执行相加运算,最后输出结果。
在线程中引入事件循环
第三版:队列 + 循环
原因:在第二版的线程模型中,所有的任务都是来自于线程内部的,如果另外一个线程想让主线程执行一个任务,第二版的线程模型无法做到。
解决:使用消息队列
消息队列是一种数据结构,可以存放要执行的任务。符合队列“先进先出”的特点
从上图可以看出,改造分三步:
- 添加一个消息队列
- IO线程中产生的新任务添加进消息队列尾部
- 渲染主线程会循环地从消息队列头部中读取任务,执行任务。
跨进程发送消息
从图中可以看出,如果其他进程想要发送任务给页面主线程,那么先通过IPC把任务发送给渲染进程的IO线程,IO线程再把任务发送给页面主线程。
页面主线程如何安全退出
在Chrome中,当页面主线程执行完成后,确定要退出当前页面时,页面主线程会设置一个退出标志的变量,在每次执行完后判断是否有退出标志,若有,则直接中断当前的所有任务。
页面使用单线程的缺点
- 如何处理高优先级的任务
若采用观察者模式,设计监听接口,则影响执行效率。若采用异步,则影响实时性。
解决方案:微任务
通常我们把消息队列中的任务称为宏任务,每个宏任务中都包含了一个微任务队列,在执行宏任务的过程中,如果 DOM 有变化,那么就会将该变化添加到微任务列表中,这样就不会影响到宏任务的继续执行,因此也就解决了执行效率的问题。
等宏任务中的主要功能都直接完成之后,这时候,渲染引擎并不着急去执行下一个宏任务,而是执行当前宏任务中的微任务,因为 DOM 变化的事件都保存在这些微任务队列中,这样也就解决了实时性问题。
- 如何解决单个任务执行时长过久,导致卡顿的问题
解决方案:JavaScript通过回调功能来规避这种问题,也就是让要执行的JavaScript任务滞后执行。
微任务
- 为什么需要微任务?
因为 JavaScript 代码不能掌控宏任务添加到队列中的位置,难以控制开始执行任务的时间。对时间精度要求较高的需求,宏任务难以胜任,所以需要微任务。
- 什么是微任务?
微任务是一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前
-
如何产生微任务?
-
使用 MutationObserver 监控某个 DOM 节点,然后再通过 JavaScript 来修改这个节点,或者为这个节点添加、删除部分子节点,当 DOM 节点发生变化时,就会产生 DOM 变化记录的微任务。
-
使用 Promise,当调用
Promise.resolve()
或者Promise.reject()
的时候,也会产生微任务。
-
-
微任务和宏任务
-
微任务和宏任务是绑定的,每个宏任务在执行时,会创建自己的微任务队列。
-
微任务的执行时长会影响到当前宏任务的时长。
-
在一个宏任务中分别创建一个用于回调的宏任务和微任务,无论什么情况下,微任务都早于宏任务执行。
-
宏任务是开会分配的工作内容,微任务是工作过程中被临时安排的内容