本文只是用于将学习到的常识做一个梳理与总结
浏览器架构
古代浏览器通常采纳多过程架构。每个过程都有独立的内存空间,互相隔离,进步浏览器的稳定性、安全性和性能。
以Chrome为例,浏览器的过程蕴含以下几个次要过程:
- 浏览器主过程: 负责协调整个浏览器的运行,包含用户界面、网络申请、子过程的创立和销毁等。
- 渲染过程: 将HTML/CSS/JS转化为用户能够交互的网页
- 网络过程: 解决网络申请、响应、DNS等
- GPU过程:负责解决图形渲染相干的工作,如2D、3D绘图等
- 插件过程:运行浏览器插件
渲染过程
对于咱们的页面来说,最重要的就是渲染过程
,它蕴含了以下的多个线程
:
- 主线程:负责解决用户输出、JavaScript 执行和页面布局计算等工作,是渲染过程中最重要的线程之一。
- 渲染线程:负责将 HTML、CSS 和 JavaScript 转换为可视化的页面,其中包含页面布局、款式计算、绘制和合成等工作。
- 合成线程:负责将页面中的多个图层合成为最终的显示内容,并将其发送到 GPU 进行渲染。
- JavaScript引擎线程:JavaScript 引擎线程负责解析和执行页面中的 JavaScript 代码
- 事件线程:负责解决用户输出事件,如鼠标点击、键盘输入等,以及页面中的事件触发和解决
- IO线程: 负责接管其余过程传进来的音讯
- …
在这当中,主线程最为忙碌,既要解决 DOM,又要计算款式,还要解决布局,同时还须要解决 JavaScript 工作以及各种输出事件。而浏览器则通过在主线程
中实现音讯队列
和事件循环系统
来调度这么多不同类型的工作。
咱们能够通过上面的图片来理解主线程
、事件循环
、音讯队列
和其余线程之间的关系
然而,音讯队列
是先进先出的。主线程
所有执行工作都来自于音讯队列
。会面临以下两个问题
1. 如何解决高优先级的工作
比方,如何监控DOM节点的变动状况(节点的插入、批改、删除等动态变化),而后依据变动来解决相应的业务逻辑。一个通用设计就是利用js设计一套监听接口,当变动产生时,渲染引擎同步调用这些接口,这是一个典型的观察者模式。
不过这个模式有个问题,因为 DOM 变动十分频繁,如果每次发生变化的时候,都间接调用相应的 JavaScript 接口,那么这个以后的工作执行工夫会被拉长,从而导致执行效率的降落。如果将这些 DOM 变动做成异步的音讯事件,增加到音讯队列的尾部,那么又会影响到监控的实时性,因为在增加到音讯队列的过程中,可能后面就有很多工作在排队了。
这也就是说,如果 DOM 发生变化,采纳同步告诉的形式,会影响当前任务的执行效率;如果采纳异步形式,又会影响到监控的实时性。
2. 如何解决单个工作执行时长过久的问题。
从图中能够看到,所有工作都是在单线程中执行的,而因为每一帧的工夫无限,如果某一个js工作十分的耗时,那么上面的工作(DOM解析、JS事件、布局计算、用户输出事件等)就须要期待很长时间。这也就是咱们页面中卡顿的由来。
第一个问题就能够通过上面的微工作
来解决
宏工作,微工作
首先,咱们须要晓得工作队列
中蕴含有以下两种类型的工作
宏工作
- 渲染事件(如解析 DOM、计算布局、绘制);
- 用户交互事件(如鼠标点击、滚动页面、放大放大等);
- JavaScript 脚本执行事件;
- 网络申请实现、文件读写实现事件。
setTimeout
的回调函数属于宏工作
微工作
Promise
的回调属于微工作
MutationObserver
的回调函数:当被察看的 DOM 节点发生变化时,MutationObserver
的回调函数会被增加到微工作队列中。queueMicrotask
办法:该办法能够将回调函数增加到微工作队列中,期待执行。该办法是 ES2020 规范中新增的。
宏工作
与微工作
的最次要区别在于它们的执行机会
宏工作
是增加一个新的工作到音讯队列
中,如果应用setTimeout
来异步执行一个操作时,工夫距离无奈精准掌控,对于一些高实时性
的需要不太合乎。比方你在程序中应用setTimeout
提早1000ms
去执行某个工作时,可能在这1000ms
中曾经触发了很多零碎级的工作,它们曾经被插入到了音讯队列
中。等到过了1000ms
后setTimeout
才将会它的回调插入到音讯队列
中,这就须要期待队列前的工作全副执行完了能力到它的回调
微工作
是 在以后宏工作
完结前再执行微工作
,每个宏工作
都关联了一个微工作队列
。所以,只有在以后宏工作
中触发了微工作
,所有微工作的回调都会被增加到微工作队列
中期待执行。这样,你再怎么交互,生成的宏工作
都会排在以后的宏工作
之后。这样,实时性
问题就解决了。
React如何利用浏览器的个性来做“并发”
在理解了后面对于浏览器的个性以及相干问题后。咱们再回到react中看React为了并发个性做了哪些改变。
time slice 与 fiber
在react16之前,始终是递归更新。而16之后,react提出了一个新的概念 time slice,便于将工作切分,而后在浏览器的闲暇工夫来执行工作,超出了闲暇工夫则将残余工作往后推。但因为递归更新中断后无奈再持续,所以react重构了它的代码,将递归更新改成了fiber这种链表构造。这样即便是暂停了,还能从暂停出的链表继续执行。这样就解决了组件单个执行工作过长
的问题。
异步更新
咱们能够在react的react-reconciler包中找到scheduleSyncCallback
办法,所有的更新操作
都保留到了syncQueue
队列中,而后通过scheduleMicrotask
这个办法创立微工作,flushSyncCallbacks
就是这个微工作的异步回调,而flushSyncCallbacks
当中执行的就是所有的更新操作
。这就解决了组件更新效率
的问题。
Scheduler 调度器
当初,有了可中断的工作,并且同步工作
被放到了微工作
中执行。而且因为个别支流浏览器刷新频率为60Hz,即每16.6ms(1000ms / 60Hz)浏览器刷新一次。
所以react须要解决的就是如何利用每一帧中预留给js线程的工夫来更新组件(在scheduler源码中,react预留了5ms)。当超过预留工夫后,react就会中断更新,期待下一帧的闲暇工夫持续从被中断的fiber
处执行。这样就尽可能的防止了工作执行工夫过长而呈现掉帧
、卡顿
的景象。
总结
react 利用浏览器的渲染过程主线程的事件循环
以及宏工作
、微工作
的特点,将原有的数据结构扭转为Fiber
这种可中断的链表构造。
并且通过将所有的更新操作
应用微工作
来执行,解决组件更新的实时性
问题。而后再实现了调度器
来实现工作的中断和持续来解决工作执行工夫过长
的问题。
参考
- 浏览器工作原理与实际
- react技术揭秘
- 从零实现React 18