深入了解React中的任务调度算法

本篇文章带大家学习一下react,深入了解下react中的任务调度算法,希望对大家有所帮助!

深入了解React中的任务调度算法

React中的任务池

React中的Fiber任务都应该知道吧,而且不同的Fiber任务有不同的优先级,React需要先处理优先级高的任务。例如,用户的点击和输入,这些都是高优先级的任务,因为用户的操作肯定希望马上就会有效果,这样才能提升用户体验。而比如animation事件的优先级肯定要低一点。新进来的高优先级任务进去队列后,React需要优先处理。【相关推荐:Redis视频教程】

为了存储这些任务,React中有两个任务池。

// Tasks are stored on a min heapvar taskQueue = [];var timerQueue = [];

taskQueue与timerQueue都是数组,前者存储的是立即要执行的任务,而后者存的则是可以延迟执行的任务。

  var newTask = {    id: taskIdCounter++, // 标记任务id    callback, // 回调函数    priorityLevel, // 任务优先级    startTime, // 任务开始时间,时间点    expirationTime, // 过期时间,时间点    sortIndex: -1, // 任务排序,取值来自过期时间,因此值越小,优先级越高  };

React中一旦来了新任务,就会先用currentTime记录当前时间(performance.now()或者Date.now()),如果任务有delay参数,那么任务开始执行时间startTime = currentTime + delay;。接下来通过startTime > currentTime如果成立,证明任务是可以延期的,那么任务进入timerQueue,否则进入taskQueue。

React中的任务调度

React怎么找到优先级最高的任务呢,以taskQueue为例,它是动态的任务池(任务队列),数据形式上就是一个数组。当然可以根据优先级进行排序,也就是Array.sort,当有新任务入队后,先排序,然后找出优先级最高的任务执行。但是Array.sort的平均时间复杂度是O(nlogn),并不是最好的解决方案。

taskQueue的newTask中排序用的是sortIndex,这个值取自过期时间expirationTime,也就意味着优先级越高的任务越需要理解执行,那么过期时间就越小,也就是说,优先级越高,过期时间就越小,sortIndex自然就越小。其实,这就是一种优先队列

1.png

优先队列

优先队列也是一种队列(首先它是一个队列,其次是尾进头出),只不过不同的是,优先队列的出队顺序是按照优先级来的;在有些情况下,可能需要找到元素集合中的最小或者最大元素,可以利用优先队列ADT来完成操作,优先队列ADT是一种数据结构,它支持插入和删除最小值操作(返回并删除最小元素)或删除最大值操作(返回并删除最大元素)。

如果最小键值元素拥有最高的优先级,那么这种优先队列叫做,升序优先队列(即总是先删除最小的元素)。类似的,如果最大键值元素拥有最高的优先级,那么这种优先队列叫作降序优先队列(即总是先删除最大的元素);由于这两种类型时对称的,所以只需要关注其中一种,如升序优先队列。

例如:买车票的时候,我们都在排队,优先级是一样的,谁在队伍前面,谁就先买票,但是这时候来了个军人,他的优先级高,直接就排在了队伍的最前面。

在React中用最小堆(小根堆,小顶堆。。。)来实现这种功能。就是把taskQueue变成最小堆,然后取出对顶任务执行,对taskQueue堆化,维持它依然是一个最小堆的数据结构。往taskQueue插入新任务的时候,也要进行堆化,始终保持它是一个最小堆。

优先队列和堆的关系

有些地方称堆为优先队列(不准确),首先它是队列,有队列的特性,也就是“先进先出”。其次这个队列中的元素是有优先级的,优先级高的会排在前面。

准确来说,堆是实现优先队列的一种方式。当然优先队列还可以用其他方式来实现。

2.png

React中的最小堆

之前我们说过堆排序是不稳定排序,但taskQueue希望这个过程是稳定的,也就是说,如果有可能两个任务的过期时间一样,那这个时候就要看谁先进入的任务池了,也就是newTask的id的值,每次来了新任务,id都会加1。

function compare(a, b) {  // Compare sort index first, then task id.  const diff = a.sortIndex - b.sortIndex;  return diff !== 0 ? diff : a.id - b.id;}

最小堆

在了解最小堆之前,先来温习一下基础知识。

二叉树

是指树中节点的度不大于2的有序树,它是一种最简单且最重要的树。

满二叉树

除最后一层无任何子节点外,每一层上的所有结点都有两个子结点的二叉树。

从图形形态上看,满二叉树外观上是一个三角形。

如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。

3.png

满二叉树,是“女儿双全”,非常圆满,所以叫满二叉树。

完美二叉树

除去叶子节点, 所有节点的度都是 2。也就是说,所有的节点的度只能是0或2。

4.png

完美二叉树,要么没有孩子,要么儿女双全。

满二叉树 VS 完美二叉树

满二叉树的英文原文:
A Full Binary Tree (FBT) is a tree in which every node other than the leaves has two children.

完美二叉树的英文原文:

A Perfect Binary Tree(PBT) is a tree with all leaf nodes at the same depth. All internal nodes have degree 2.

国外的所有书籍参考的是最早翻译的关于满二叉树,和完美二叉树的教材,但是最早翻译的文章翻译错了。现在国内的话,我们只能将错就错了(所有人都错,那错的也就是对的了。比如说客。。。)。如果要和外国友人讨论这两个概念,就要注意了哦。

完全二叉树

A Complete Binary Tree (CBT) is a binary tree in which every level,except possibly the last, is completely filled, and all nodes are as far left as possible.

算家云 算家云

高效、便捷的人工智能算力服务平台

算家云 37 查看详情 算家云

一棵深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。

除了最后一层外, 所有层都完美填充最后一层所有叶子节点靠左对齐

5.png

6.png

堆是一棵完全二叉树。

堆总是满足下列性质:

堆总是一棵完全二叉树;堆中某个节点的值总是不大于或不小于其父节点的值;

还要先认识下大根堆和小根堆,完全二叉树中所有节点均大于(或小于)它的孩子节点,所以这里就分为两种情况,最大堆和最小堆。

最大堆

如果所有节点**「大于」孩子节点值,那么这个堆叫做「最大堆」**,堆的最大值在根节点。

最小堆

如果所有节点**「小于」孩子节点值,那么这个堆叫做「最小堆」**,堆的最小值在根节点。

7.png

堆通常是一个可以被看做一棵 完全二叉树 的数组对象。 当然,二叉树也可以用数组表示。

8.png

堆的实现

核心思想是,先建堆,后调整。

创建堆

对于二叉树(数组表示),我们从下往上进行调整,从**「第一个非叶子节点」**开始向前调整,对于调整的规则如下:

建堆是一个O(n)的时间复杂度过程。

①从第一个非叶子节点开始判断交换下移(shiftDown),使得当前节点和子孩子能够保持堆的性质

②但是普通节点替换可能没问题,对如果交换打破子孩子堆结构性质,那么就要重新下移(shiftDown)被交换的节点一直到停止。

9.png

调整堆

堆构造完成,取第一个堆顶元素为最小(最大),剩下左右孩子依然满足堆的性值,但是缺个堆顶元素,如果给孩子调上来,可能会调动太多并且可能破坏堆结构。

① 所以索性把最后一个元素放到第一位。这样只需要判断交换下移(shiftDown),不过需要注意此时整个堆的大小已经发生了变化,我们在逻辑上不会使用被抛弃的位置,所以在设计函数的时候需要附带一个堆大小的参数。

② 重复以上操作,一直堆中所有元素都被取得停止。

10.png

而堆算法复杂度的分析上,之前建堆时间复杂度是O(n)。而每次删除堆顶然后需要向下交换,每个个数为logn个。这样复杂度就为O(nlogn),总的时间复杂度为O(n)+O(nlogn)=O(nlogn)。

堆的应用场景

堆适合维护集合的最值。

堆pop出一个元素后,再次调整获取堆顶元素(也就是第二个最值)的花销比较低,因为pop出元素后,堆是一个半成品,在一个半成品上获取第二个最值的cost当然比较低,时间复杂度为O(logn),但如果遍历一遍找到第二个最值的话,时间复杂度为O(n)。

代码实现

代码采用Javascript ES6的写法。

代码

class Heap {    constructor(data, comp) {       this.data = data ? data : [];        // 比较规则:更加灵活,可以比较数值,也可以比较对象       this.compartor = comp ? comp : (a-b) => a-b;        // 调整为堆(优先队列)       this.heapify();    }     heapify() {       if(this.size() =0; i--) {          // 调整堆, 向下调整也可以用递归来实现,这里用迭代来实现          this.shiftDown(i);       }    }     // 向下调整    shiftDown(i) {       let left = 2*i +1;       let right = 2*i +2;        let len = this.size();       while(i < len) {          let findIndex = i;          // 左孩子更“大”          if(left < len && this.compartor(this.data[left], this.data[findIndex]) < 0) {             findIndex = left;          }          // 右孩子更“大”          if(right < len && this.compartor(this.data[right], this.data[findIndex]) =0 ) {          let findIndex = i;          if(this.compartor(this.data[parentIndex], this.data[findIndex]) > 0) {             findIndex = parentIndex;          }           if(findIndex !== i) {             [this.data[i], this.data[findIndex]] = [this.data[findIndex], this.data[i]];             i = findIndex;             parentIndex = Math.floor((i-1)/2);          }          else {              break;          }       }    }     // 获取堆中所有元素的个数    size(){        return this.data.length;    }     // 获取堆首部元素    peek(){        if(!this.size()) return null;         return this.data[0];    }     // 往堆中添加一个元素    push(x){       this.data.push(x);              this.shiftUp(this.data.length-1);    }     // 从堆里弹出堆首元素    pop(){      if(!this.size()) return null;       let res = this.data[0];       if(this.size() == 1) {         this.data.pop();      }      else {          this.data[0] = this.data[this.data.length-1];          this.data.length = this.data.length-1;          this.shiftDown(0);      }       return res;    } }

测试

 let arr = [2,9,8,6,3,10,5,7,4,1]; let comp = (a, b) => a-b; let heap = new Heap(arr, comp);  let res = []; while(heap.size()) {    res.push(heap.pop()); } console.log(res);

arr里的元素也可以是一个对象。

React源码部分

React源码中的目录packages/scheduler,就是React的任务调度模块相关的代码。

https://github.com/facebook/react/blob/17.0.2/packages/scheduler/src/Scheduler.jshttps://github.com/facebook/react/blob/17.0.2/packages/scheduler/src/SchedulerMinHeap.js

11.png

/** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow strict */type Heap = Array;type Node = {|  id: number,  sortIndex: number,|};export function push(heap: Heap, node: Node): void {  const index = heap.length;  heap.push(node);  siftUp(heap, node, index);}export function peek(heap: Heap): Node | null {  const first = heap[0];  return first === undefined ? null : first;}export function pop(heap: Heap): Node | null {  const first = heap[0];  if (first !== undefined) {    const last = heap.pop();    if (last !== first) {      heap[0] = last;      siftDown(heap, last, 0);    }    return first;  } else {    return null;  }}function siftUp(heap, node, i) {  let index = i;  while (true) {    const parentIndex = (index - 1) >>> 1;    const parent = heap[parentIndex];    if (parent !== undefined && compare(parent, node) > 0) {      // The parent is larger. Swap positions.      heap[parentIndex] = node;      heap[index] = parent;      index = parentIndex;    } else {      // The parent is smaller. Exit.      return;    }  }}function siftDown(heap, node, i) {  let index = i;  const length = heap.length;  while (index < length) {    const leftIndex = (index + 1) * 2 - 1;    const left = heap[leftIndex];    const rightIndex = leftIndex + 1;    const right = heap[rightIndex];    // If the left or right node is smaller, swap with the smaller of those.    if (left !== undefined && compare(left, node) < 0) {      if (right !== undefined && compare(right, left) < 0) {        heap[index] = right;        heap[rightIndex] = node;        index = rightIndex;      } else {        heap[index] = left;        heap[leftIndex] = node;        index = leftIndex;      }    } else if (right !== undefined && compare(right, node) < 0) {      heap[index] = right;      heap[rightIndex] = node;      index = rightIndex;    } else {      // Neither child is smaller. Exit.      return;    }  }}function compare(a, b) {  // Compare sort index first, then task id.  const diff = a.sortIndex - b.sortIndex;  return diff !== 0 ? diff : a.id - b.id;}

我们自己实现的最小堆和React中的实现略有不同,但是思路是一样的,只是代码写法不同而已。

总结

React中的任务调度是用最小堆来实现的,如果我们之前就对最小堆有一定了解,那在学习这块内容的时候就会更快一点。个人认为,前期知识积累是多么重要啊,但是这个过程可能会比较枯燥。 这个时候,是不是觉得自己也会一些算法了,其实这些算法是入门级别的,甚至还没有入门。因为在React的任务调度场景中,要实现的需求是非常明确的,而且要采用什么样的数据结构和算法也是明确的。在实际的一些场景中,我们知道了具体的需求,但是并不知道用什么数据结果和算法,就需要把需求抽象一下,根据抽象的数据模型来设计具体的数据结构和算法,这些才是重点。

更多编程相关知识,请访问:编程视频!!

以上就是深入了解React中的任务调度算法的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/549508.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
千牛网页版卖家工作台网址_千牛网页官方客服服务平台入口
上一篇 2025年11月9日 19:20:47
PHP跨平台开发中代码移植的难点和解决方案
下一篇 2025年11月9日 19:20:50

相关推荐

  • React组件中动态属性值的管理与同步:利用状态实现受控组件

    本教程旨在解决react组件中动态属性值同步使用的问题。我们将探讨如何利用react的`usestate` hook来管理组件内部状态,从而实现一个属性的值动态地影响另一个属性,并构建出可预测、易于维护的受控组件。文章将通过具体代码示例,详细阐述从初始化状态到处理状态更新的完整过程,并强调受控组件在…

    2026年5月10日
    000
  • 基于两数组数据计算结果排序的 React 教程

    本教程针对 React 应用中需要根据两个独立数组的数据计算结果进行排序的场景,提供了一种高效的解决方案。通过使用 JavaScript 的 `reduce` 和 `map` 方法,将两个数组根据唯一标识符进行合并,从而简化排序逻辑,提高代码的可读性和可维护性。避免了复杂的嵌套循环或同步迭代,提供了…

    2026年5月10日
    000
  • 解决React中按钮点击不显示弹出表单的问题:状态管理与语法修正

    本教程旨在解决react应用中点击按钮后弹出表单未能正确渲染的问题。核心在于识别并修正代码中的语法错误以及未定义的react状态管理函数。我们将详细探讨如何使用`usestate`等react hooks来声明和管理组件状态,确保交互逻辑的正确实现,并提供结构清晰的代码示例,帮助开发者构建功能完善的…

    2026年5月10日
    000
  • Go语言与Microsoft SharePoint集成指南

    Go语言可以有效集成Microsoft SharePoint,主要通过两种途径:一是利用SharePoint提供的RESTful API进行数据交互,Go的标准HTTP客户端库即可轻松实现;二是通过SharePoint应用模型开发自托管应用,这种模型支持使用包括Go在内的任何语言编写后端逻辑。 1.…

    2026年5月10日
    000
  • javascript生命周期钩子是什么_组件有哪些关键阶段?

    JavaScript原生无生命周期钩子,这是Vue、React等框架为组件设计的机制;Vue按创建、挂载、更新、卸载四阶段提供对应钩子,React类组件有明确生命周期方法,函数组件则通过useEffect模拟,其核心价值在于精准控制执行时机以避免DOM操作错误和内存泄漏。 JavaScript 本身…

    2026年5月10日
    000
  • React Redux 中 useSelector 的自动订阅与取消订阅机制

    React Redux 中 useSelector 的自动订阅与取消订阅机制React Redux 中 useSelector 的自动订阅与取消订阅机制React Redux 中 useSelector 的自动订阅与取消订阅机制React Redux 中 useSelector 的自动订阅与取消订阅机制

    本文深入探讨 react redux 中 `useselector` hook 的核心机制。它详细解释了 `useselector` 如何在组件挂载时自动订阅 redux store 的状态更新,并在组件卸载时智能地取消订阅。这确保了应用程序的性能和内存效率,避免了对已卸载组件进行不必要的更新,从而…

    2026年5月10日 用户投稿
    100
  • 在 React 中实现用户输入停止检测的防抖策略

    本文详细介绍了在 React 应用中如何精确检测用户停止输入行为。通过引入防抖(Debounce)函数,可以有效优化输入事件处理,避免频繁触发不必要的网络请求或状态更新。文章提供了基于 React Hooks 的防抖实现示例,并探讨了其在提升用户体验和系统性能方面的应用,确保在用户停止输入指定时间后…

    用户投稿 2026年5月10日
    000
  • JavaScript中的标签模板字面量(Tagged Templates)有哪些高级用法?

    标签模板通过自定义函数实现复杂逻辑,如html函数转义防止XSS,css函数生成唯一类名封装样式,结合哈希值隔离组件样式,确保安全与模块化。 标签模板字面量不只是字符串拼接工具,它能结合函数实现更复杂的逻辑处理。通过自定义标签函数,你可以解析模板中的表达式和静态部分,从而实现如国际化、样式封装、安全…

    2026年5月10日
    000
  • 深入理解React组件命名规范:解决组件不渲染的常见陷阱

    本教程深入探讨react组件命名约定在组件渲染中的关键作用。我们将解释为何自定义组件名必须以大写字母开头(pascalcase),以避免与原生html元素混淆。通过对比错误和正确的代码示例,教程将指导开发者如何遵循这一核心规范,从而解决组件不显示、`is defined but never used…

    2026年5月10日
    000
  • 优化React-Redux应用中的用户与受保护数据按需加载

    本教程旨在解决React-Redux应用中用户数据和受保护API密钥在用户未登录时仍被请求,导致401错误的问题。通过引入条件性Redux状态初始化和动作分发逻辑,确保只有在用户被认为已认证时才发起相关的API请求,从而优化应用性能,减少不必要的网络流量和控制台错误。 在构建现代Web应用时,尤其是…

    2026年5月10日
    000
  • 全栈JS代码怎么结构化_全栈JavaScript项目代码结构与规范指南

    采用分层+功能划分的目录结构,明确分离前后端代码;2. 遵循单一职责原则,路由、控制器、服务与模型各司其职;3. 统一命名规范并集成ESLint+Prettier保证代码风格一致;4. 使用环境变量管理配置,通过脚本实现自动化构建与并发启动服务。 全栈JavaScript项目涉及前端、后端、数据库交…

    2026年5月10日
    000
  • JavaScript模块加载机制_JavaScript代码组织规范

    现代前端推荐使用ES Modules,通过import和export实现静态依赖管理,配合合理目录结构与命名规范提升可维护性,注意浏览器与Node.js的运行差异。 JavaScript 的模块加载机制和代码组织规范是现代前端开发中的核心基础。随着项目规模扩大,良好的模块化设计能提升代码可维护性、复…

    2026年5月10日
    000
  • 前端实现记住密码与自动填充_javascript技巧

    正确使用表单标签与属性、支持“记住我”功能、避免破坏自动填充机制、测试浏览器兼容性可实现稳定自动填充。1. 使用标准input类型并设置autocomplete属性为username和current-password;2. 登录成功后通过localStorage保存用户名,页面加载时恢复;3. 避免…

    2026年5月10日
    000
  • 深入理解React中Refs、DOM组件与类组件实例的Ref转发机制

    本文旨在澄清react中“dom组件”的概念,并深入探讨refs在原生dom元素和自定义组件(特别是类组件实例)之间的转发机制。我们将解析官方文档中的常见困惑,并通过示例代码演示如何正确地将refs转发给不同的组件类型,从而帮助开发者更好地利用refs进行dom或组件实例的直接操作。 在React开…

    2026年5月10日
    000
  • Redux Dispatch 不更新状态的排查与解决

    本文旨在帮助开发者诊断和解决 Redux 应用中 dispatch 函数调用后状态未更新的问题。通过分析常见的错误配置和代码实现,提供逐步排查方案和修正建议,确保 Redux 状态管理的正确性和可靠性。 在 Redux 应用开发中,dispatch 函数用于触发状态变更,如果 dispatch 调用…

    2026年5月10日
    100
  • React中正确处理Select元素OnChange事件

    在React应用中,正确监听select下拉菜单的值变化是常见的需求。本文将详细阐述,与原生HTML的onchange属性不同,React中应使用驼峰命名法的onChange属性来捕获此类事件。我们将通过示例代码演示如何结合React的状态管理,实现对select元素值的有效监听和响应,确保组件行为…

    2026年5月10日
    100
  • JavaScript中动态生成HTML链接:正确使用模板字面量嵌入URL

    本文深入探讨了在javascript中动态生成html链接时,如何正确地将变量(尤其是url)嵌入到`href`属性中。通过分析常见的错误,即混淆javascript的模板字面量与框架特有的模板语法,文章详细演示了使用es6模板字面量`${}`进行字符串插值的正确方法,确保动态链接能够被浏览器正确解…

    2026年5月10日
    000
  • HTML代码怎么实现错误边界_HTML代码错误边界处理方法与异常捕获策略

    答案:通过JavaScript模拟错误边界,结合try…catch、onerror事件、Promise.catch()及全局监控工具,可有效捕获并隔离HTML应用中的错误,防止功能失效。 HTML本身并没有直接提供像JavaScript那样的“错误边界”概念。HTML主要负责结构和内容,…

    2026年5月10日
    100
  • 在JavaScript中,如何实现数据的不可变性(Immutability)?

    使用const声明变量可防止重新赋值,但无法阻止对象内部修改,需结合扩展运算符、不可变数组方法和Object.freeze实现深层不可变,关键在于始终返回新对象而非修改原数据。 在JavaScript中,实现数据的不可变性意味着避免直接修改现有对象或数组,而是通过创建新对象来反映状态变化。虽然Jav…

    2026年5月10日
    200
  • 解决 React Native Android 应用启动时出现的伪启动页问题

    本文旨在解决 React Native Android 应用在启动时,先显示一个带有应用图标的黑色伪启动页,然后再显示自定义启动页的问题。通过修改 Android 项目的 `styles.xml` 文件,禁用应用的预览窗口,即可有效避免此问题,提升用户体验。 在开发 React Native 应用时…

    2026年5月10日
    100

发表回复

登录后才能评论
关注微信