likes
comments
collection
share

React Fiber源码笔记(一):概念-浏览器帧渲染React Fiber 架构理念前置知识:复习浏览器事件循环机制

作者站长头像
站长
· 阅读数 25

前言:最近离职准备面试,把之前写的笔记整理一下发出来,本人能力有限,如有错误的地方尽情指正(免责声明) 博客链接:pionpill

这篇文章开始会逐步解析 React Fiber 架构的源代码,简单说一下本系列文章的结构:

  • 概念章 (理念性文章)
    • 浏览器帧渲染: 简单介绍一些基础的浏览器概念。
    • Fiber架构: 简单介绍 Fiber 架构的理念。
  • vDOM 章 (数据结构篇)
    • JSX 解析: 介绍 JSX -> ReactElement 的过程,给下一篇打基础
    • FiberNode: 介绍 FiberNode, FiberRootNode 两种数据结构及其构建过程,阐述几个重要的属性
  • scheduler 章
    • 优先级与准备阶段: scheduler 的一些基础概念以及 FiberNode 执行更新任务的准备阶段。
    • 调度流程: scheduler 在时间片内处理异步任务的流程
  • 过渡章
    • 双缓存机制: Fiber 架构的核心机制,是后面所有文档的基础
    • 冲刷副作用: react 副作用异步执行逻辑,这个逻辑在后续各个阶段都会被频繁提到
  • render 章
    • 准备阶段: 进入 render 核心阶段前的准备阶段
    • beginWork: Fiber 树递阶段,开始从根节点到子节点进行 diff 比较,尝试替换 FiberNode
    • completeWork: Fiber 树归阶段,更新 FiberNode
  • commit 章
    • 概述: commit 阶段整体逻辑
    • DOM阶段: 执行 DOM 各阶段的钩子/API函数,触发被动副作用 (useState 导致的副作用)

如有错误或者表述不明的地方请联系本人。React 源代码较为难读,国内相关文章比较少,多个模块互相依赖。如果一遍看不懂,建议往后看几章,再回过来看会恍然大悟(我写的时候有这种感觉)。

这篇文章简述浏览器在帧时间内的操作逻辑,说明一些基本概念,方便后续解析 Fiber 框架。系列文章默认浏览器内核为 chromium,JS 引擎为 v8 引擎

浏览器多进程体系

现代浏览器基本都是多进程(process)架构,以 chrome 浏览器为例,它主要包含了以下几种类型的进程:

  • 浏览器进程(browser process): 主进程,负责管理合协调其他进程,只有一个。
  • 渲染进程(render process): 每个标签页,扩展页,iframe 都有一个,负责解析和渲染网页内容,解析与执行 JS 代码,与用户交互。
  • GPU 进程(GPU process): 负责处理与图形相关的任务,例如页面绘制,CSS 动画。
  • 插件进程(plugin process): 运行诸如 flash,pdf 等插件。
  • 存储进程(utility process): 负责处理存储相关的任务: Cookie, 缓存...
  • 扩展进程(Extension Process):提供额外的功能和服务。

假设我们打开一个使用 chromium 内核的 Edge 浏览器(不打开 Chrome 的原因是 Chrome 浏览器所有进程都叫 Google Chrome),展开 Microsoft Edge 可能会有如下进程:

Microsoft Edge
  |- 浏览器       // 浏览器进程
  |- GPU 进程     // GPU 进程
  |- 标签页: xxx  // 渲染进程
  |- 标签页: xxx  // 渲染进程
  |- 标签页: xxx  // 渲染进程
  |- 扩展: React devtools       // 插件进程
  |- 实用工具: Storage Service  // 存储进程

这一系列文章只关注渲染进程,渲染进程负责处理 HTML,CSS,JS。每个渲染进程都会有一个 v8 引擎负责解析与执行 JavaScript 代码。渲染进程(基本)只和主进程与GPU进程通信。

渲染进程

这里的所有进程都可以是多线程(thread)的,渲染进程中常见的线程包括:

  • GUI 渲染线程: 将 HTML 和 CSS 转换为可视化页面。
  • JS 引擎线程: 执行与解析 JS 代码,顾名思义有一个 v8 引擎。
  • 事件线程: 处理用户输入,定时器事件和异步操作的回调。
  • 定时触发器线程: setInterval与setTimeout所在线程,由于 JS 引擎是单线程的,因此要单独开一个线程。
  • 异步http请求线程: 处理 http 请求的。
  • 工作线程: 执行 Web Workers,在后台执行复杂的计算任务,不影响JS线程。

这里浏览器引擎不同或者版本不同可能存在一定差异。下文只关注于 GUI 线程和 JS 线程

这些线程中有几个需要注意的地方:

  • JS 线程是单线程的,并且一个渲染进程一定有一个 JS 线程(即使啥都不干)。由于 JS 线程是单线程的,可能存在任务阻塞问题,才将一些任务委派给了事件线程,定时触发器线程...
  • GUI 线程和 JS 线程是互斥的。因为 GUI 线程会和 JS 线程都会操作 DOM。

简单说一下渲染线程,渲染线程可以被分为以下子阶段:

构建 DOM 树 -> 样式计算 -> 布局 -> 分层 -> 绘制 -> 分块 -> 光栅 -> 合成
           layout              |                paint

由于浏览器不同,在很多文章中,还有一个主线程的概念。主线程是指 GUI 线程或者 JS 线程,因为这两个线程是互斥的,同一时间只能执行一个,而这两线程又承担了绝大多数任务,因此称某一时刻正在执行的 JS 线程或 GUI 线程为主线程。也有说 JS 引擎 GUI 渲染在一个主线程中调度;或者说 GUI 线程就是主线程,但是 GUI 线程是没有执行 JS 代码能力的,很奇怪。总而言之,我们记住渲染过程和 JS 解析执行操作互斥,主线程是一个可以包含两者功能的概念就行了。

事件循环

下文会涉及到的几个概念:

  • 主线程: "进入主线程"表示会在一轮事件循环中执行;"不进入主线程"表示不在该轮事件循环中执行,一般是指放到容器中不处理。
  • 任务队列: 一种数据结构,先进先出地存储异步任务。

JS 和 JS 引擎都是单线程的,为了防止多任务阻塞,产生了事件循环机制。事件循环过程中有两类任务:

  • 同步任务: 进入主线程,排队执行的任务。
  • 异步任务: 不进入主线程,进入异步任务队列。

异步任务又分为两种:

  • 宏任务: 由浏览器,Node等宿主环境发起的任务。
    • 定时器任务 setTimeout/setInterval,setImmediate...
    • I/O
    • UI 渲染
    • requestAnimationFrame
    • script 标签
  • 微任务: 由 JS 引擎发起的任务
    • promise 的回调函数,注意 Promise 本身是同步的。
    • Object.observe / MutationObserver
    • process.nextTick(node.js)
    • Async/Await

遇到一串 JS 代码,事件循环的执行逻辑如下:

  • 开始执行代码
  • 找到同步任务,执行
    • 找到微任务,加入到微任务队列
    • 找到宏任务,加入到宏任务队列
  • 同步任务执行完毕
  • 依此取微任务队列中的所有任务执行
  • 取下一个宏任务(将其变成同步代码),开始下一轮事件循环

如果将首次执行的同步代码看作一个宏任务转变来的,那么一次事件循环就是执行一个(先前的)宏任务,与所有的微任务。甚至可以这样理解: 宏任务会触发事件循环,微任务处理异步操作。

帧渲染

前面说了那么多,都是为了帧渲染做铺垫。

浏览器一般是 60fps,即一秒渲染 60 帧,一帧的渲染时间就是 16.6ms,如果刷新率有变化,这个时间也会发生对应的变化,我们将渲染一帧可用的时间称为帧时间。

帧时间内会干以下事:

React Fiber源码笔记(一):概念-浏览器帧渲染React Fiber 架构理念前置知识:复习浏览器事件循环机制

我们一般将 layout 和 paint 两个过程合称为渲染过程

上面蓝色部分 JS 引擎会参与,其中两 rAF 和 idle 含义如下(并不是所有的浏览器都有这两个过程):

  • rAF: 两个 API 最初的设计都是用于控制动画的,也可以用于在渲染过程前执行 JS 代码,相关链接: requestAnimationFrameMutationObserver
  • idle: 这里是帧时间的空闲阶段,如果某帧执行完了发现还有多余的时间,会在这里执行一些操作。

所以说,浏览器帧时间内,JS 引擎会先后执行:

  • 取出上一个宏任务
  • 执行同步代码与异步微任务
  • 看看有没有 rAF 相关的回调,有就执行
  • 如果有空闲时间,执行 idle

如果某个任务执行时间过长,在帧时间内无法完成,就会一直执行下去,直到完成该任务。这种执行时间超过帧时间的任务叫做长任务。例如某个任务执行了 70ms,那么就占用了 5 帧。本来 1 帧就会返回结果,现在却要 5 帧,就有 4 帧无法进行渲染操作,画面就会卡顿。

在浏览器 devtools 中有一个性能(performance)界面,可以录制一段时间内的操作,并显示堆栈图。

转载自:https://juejin.cn/post/7403552860456140826
评论
请登录