本文只是对学习到的知识进行梳理和总结。浏览器架构现代浏览器通常使用多进程架构。每个进程都有独立的内存空间,相互隔离,提高了浏览器的稳定性、安全性和性能。以Chrome为例,浏览器进程包括以下几个主要进程:浏览器主进程:负责协调整个浏览器的运行,包括用户界面、网络请求、子进程的创建和销毁等渲染进程:转换将HTML/CSS/JS转化为用户可以交互的网页网络进程:处理网络请求、响应、DNS等GPU进程:负责图形渲染相关的任务,如2D、3D绘图等插件进程:运行浏览器插件渲染过程对于我们的页面来说,最重要的就是渲染过程,它包括以下多个线程:主线程:负责处理用户输入、JavaScript执行和页面布局计算,是最重要的部分渲染进程的线程之一。渲染线程:负责将HTML、CSS、JavaScript转换成可视化页面,包括页面布局、样式计算、绘图、合成等任务。合成线程:负责将页面中的多个图层合成为最终的显示内容,并发送给GPU进行渲染。JavaScript引擎线程:JavaScript引擎线程负责解析和执行页面中的JavaScript代码事件线程:负责处理用户输入事件,如鼠标点击、键盘输入等,以及页面IO中的事件触发和处理thread:负责接收其他进程传入的消息message...其中,主线程最忙,处理DOM、计算样式、布局,以及JavaScript任务和各种输入事件。另一方面,浏览器通过在主线程中实现消息队列和事件循环系统来调度如此多不同类型的任务。我们可以通过下图了解主线程、事件循环、消息队列和其他线程之间的关系。但是,消息队列是先进先出的。主线程的所有执行任务都来自于消息队列。你将面临以下两个问题:1.如何处理高优先级任务比如如何监听DOM节点的变化(节点插入、修改、删除等动态变化),然后处理相应的根据业务逻辑的变化。一个常见的设计是用js设计一套监控界面。当发生变化时,渲染引擎会同步调用这些接口。这是典型的观察者模式。但是,这种模式有一个问题,因为DOM的变化非常频繁。如果每次发生变化都直接调用相应的JavaScript接口,会延长当前任务的执行时间,导致执行效率下降。如果将这些DOM变化做成异步消息事件添加到消息队列的尾部,会影响监控的实时性,因为在添加到消息队列的过程中,前面可能有很多任务排队.也就是说,如果DOM发生变化,使用同步通知的方式会影响当前任务的执行效率;如果采用异步方式,会影响监控的实时性。2、如何解决单个任务执行时间过长的问题。从图中可以看出,所有的任务都是在单线程中执行的,并且由于每一帧的时间有限,如果某个js任务非常耗时,那么下面的任务(DOM解析,JS事件,布局计算、用户输入事件等)需要很长时间等待。这是我们页面滞后的根源。第一个问题可以通过以下微任务来解决。首先,我们要知道任务队列包含以下两类任务。宏任务渲染事件(如解析DOM、计算布局、绘制);用户交互事件(如鼠标点击、页面滚动、放大缩小等);JavaScript脚本执行事件;网络请求完成、文件读写完成事件。setTimeout的回调函数属于macrotask,microtask的Promise回调属于microtask的回调函数MutationObserver:当观察到的DOM节点发生变化时,MutationObserver的回调函数会加入到microtask队列中。queueMicrotask方法:该方法可以将回调函数添加到微任务队列中等待执行。这种方法是ES2020标准中新增的。宏任务和微任务之间的主要区别在于它们的执行时间。宏任务向消息队列添加新任务。如果使用setTimeout异步执行操作,则无法准确控制时间间隔。对于一些实时性要求高的不完全是。比如当你在程序中使用setTimeout延迟1000ms执行某个任务时,在这1000ms期间可能已经触发了很多系统级的任务,并被插入到消息队列中。1000ms后,setTimeout将其回调插入到消息队列中,这需要等待队列前面的所有任务执行完毕后,回调microtask是执行当前macrotask结束前的microtask,每个Macrotasks关联一个微任务队列。因此,只要在当前macrotask中触发了microtasks,所有microtasks的回调都会加入到microtask队列中执行。这样无论怎么交互,生成的宏任务都会排在当前宏任务之后。这样就解决了实时性问题。在了解了浏览器的特性和相关问题后,React是如何利用浏览器的特性来做“并发”的。我们回过头来看React,看看React对并发特性做了哪些改变。时间片和光纤在react16之前递归更新。16之后,react提出了一个新的概念时间片,方便划分任务,然后在浏览器的空闲时间内执行任务,超过空闲时间后推回剩余的任务。但是,由于递归更新中断,无法再继续,react对其代码进行了重构,将递归更新改为类似fiber的链表结构。这样即使挂起,也可以从挂起的链表继续执行。这样就解决了单个组件执行任务时间过长的问题。对于异步更新,我们可以在react的react-reconciler包中找到scheduleSyncCallback方法。所有更新操作都保存在syncQueue队列中,然后通过scheduleMicrotask方法创建微任务。flushSyncCallbacks是这个microtask的异步回调,flushSyncCallbacks执行的都是update操作。这样就解决了组件更新效率的问题。Scheduler调度器现在有可中断的任务,同步任务被放入微任务中执行。又因为一般主流浏览器刷新频率为60Hz,即浏览器每16.6ms(1000ms/60Hz)刷新一次。那么react需要解决的就是如何利用每一帧中为js线程预留的时间来更新组件(在scheduler源码中,react预留了5ms)。当超过保留时间时,react会中断更新,等待下一帧的空闲时间从中断的fiber继续执行。这样就可以避免任务执行时间过长导致的掉帧卡顿现象。总结React利用浏览器渲染进程主线程的事件循环,以及宏任务和微任务的特点,将原来的数据结构变成了像Fiber一样可中断的链表结构。并且通过使用微任务来执行所有的更新操作,解决了组件更新的实时性问题。然后实现调度器完成任务的中断和继续,解决任务执行时间过长的问题。参考浏览器工作原理与实践,揭开React技术的秘密,从零开始实现React18
