关于前端:React如何利用浏览器的事件循环来实现并发特性

本文只是用于将学习到的常识做一个梳理与总结

浏览器架构

古代浏览器通常采纳多过程架构。每个过程都有独立的内存空间,互相隔离,进步浏览器的稳定性、安全性和性能。

以Chrome为例,浏览器的过程蕴含以下几个次要过程:

  • 浏览器主过程: 负责协调整个浏览器的运行,包含用户界面、网络申请、子过程的创立和销毁等。
  • 渲染过程: 将HTML/CSS/JS转化为用户能够交互的网页
  • 网络过程: 解决网络申请、响应、DNS等
  • GPU过程:负责解决图形渲染相干的工作,如2D、3D绘图等
  • 插件过程:运行浏览器插件

渲染过程

对于咱们的页面来说,最重要的就是渲染过程,它蕴含了以下的多个线程:

  1. 主线程:负责解决用户输出、JavaScript 执行和页面布局计算等工作,是渲染过程中最重要的线程之一。
  2. 渲染线程:负责将 HTML、CSS 和 JavaScript 转换为可视化的页面,其中包含页面布局、款式计算、绘制和合成等工作。
  3. 合成线程:负责将页面中的多个图层合成为最终的显示内容,并将其发送到 GPU 进行渲染。
  4. JavaScript引擎线程:JavaScript 引擎线程负责解析和执行页面中的 JavaScript 代码
  5. 事件线程:负责解决用户输出事件,如鼠标点击、键盘输入等,以及页面中的事件触发和解决
  6. 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中曾经触发了很多零碎级的工作,它们曾经被插入到了音讯队列中。等到过了1000mssetTimeout才将会它的回调插入到音讯队列中,这就须要期待队列前的工作全副执行完了能力到它的回调

微工作是 在以后宏工作完结前再执行微工作,每个宏工作都关联了一个微工作队列。所以,只有在以后宏工作中触发了微工作,所有微工作的回调都会被增加到微工作队列中期待执行。这样,你再怎么交互,生成的宏工作都会排在以后的宏工作之后。这样,实时性问题就解决了。

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

【腾讯云】轻量 2核2G4M,首年65元

阿里云限时活动-云数据库 RDS MySQL  1核2G配置 1.88/月 速抢

本文由乐趣区整理发布,转载请注明出处,谢谢。

您可能还喜欢...

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据