title: 链表操作

React 算法之链表操作

概念

来自 wiki 上的解释: 链表(Linked list)是一种常见的基础数据结构, 是一种线性表, 但是并不会按线性的顺序存储数据, 而是在每一个节点里存到下一个节点的指针(Pointer).由于不必须按顺序存储,链表在插入的时候可以达到 O(1)的复杂度, 但是查找一个节点或者访问特定编号的节点则需要 O(n)的时间.

  1. 单向链表: 每个节点包含两个域, 一个信息域和一个指针域. 这个指针指向列表中的下一个节点, 而最后一个节点则指向一个空值.
  2. 双向链表: 每个节点有两个连接, 一个指向前一个节点(第一个节点指向空值), 而另一个指向下一个节点(最后一个节点指向空值).
  3. 循环链表: 在单向链表的基础上, 首节点和末节点被连接在一起.

React 算法之链表操作 - 图1

基本使用

  1. 节点插入, 时间复杂度O(1)
  2. 节点查找, 时间复杂度O(n)
  3. 节点删除, 时间复杂度O(1)
  4. 反转链表, 时间复杂度O(n)
  1. // 定义Node节点类型
  2. function Node(name) {
  3. this.name = name;
  4. this.next = null;
  5. }
  6. // 链表
  7. function LinkedList() {
  8. this.head = new Node('head');
  9. // 查找node节点的前一个节点
  10. this.findPrevious = function(node) {
  11. let currentNode = this.head;
  12. while (currentNode && currentNode.next !== node) {
  13. currentNode = currentNode.next;
  14. }
  15. return currentNode;
  16. };
  17. // 在node后插入新节点newElement
  18. this.insert = function(name, node) {
  19. const newNode = new Node(name);
  20. newNode.next = node.next;
  21. node.next = newNode;
  22. };
  23. // 删除节点
  24. this.remove = function(node) {
  25. const previousNode = this.findPrevious(node);
  26. if (previousNode) {
  27. previousNode.next = node.next;
  28. }
  29. };
  30. // 反转链表
  31. this.reverse = function() {
  32. let prev = null;
  33. let current = this.head;
  34. while (current) {
  35. const tempNode = current.next;
  36. // 重新设置next指针, 使其指向前一个节点
  37. current.next = prev;
  38. // 游标后移
  39. prev = current;
  40. current = tempNode;
  41. }
  42. // 重新设置head节点
  43. this.head = current;
  44. };
  45. }

React 当中的使用场景

在 react 中, 链表的使用非常高频, 主要集中在fiberhook对象的属性中.

fiber 对象

react 高频对象中对fiber对象的属性做了说明, 这里列举出 4 个链表属性.

  1. effect链表(链式队列): 存储有副作用的子节点, 构成该队列的元素是fiber对象

    • fiber.nextEffect: 单向链表, 指向下一个有副作用的 fiber 节点.
    • fiber.firstEffect: 指向副作用链表中的第一个 fiber 节点.
    • fiber.lastEffect: 指向副作用链表中的最后一个 fiber 节点.

    React 算法之链表操作 - 图2

    注意: 此处只表示出链表的结构示意图, 在fiber 树构造章节中会对上图的结构进行详细解读.

  2. updateQueue链表(链式队列): 存储将要更新的状态, 构成该队列的元素是update对象

    • fiber.updateQueue.pending: 存储state更新的队列(链式队列), class类型节点的state改动之后, 都会创建一个update对象添加到这个队列中. 由于此队列是一个环形队列, 为了方便添加新元素和快速拿到队首元素, 所以pending指针指向了队列中最后一个元素.

    React 算法之链表操作 - 图3

    注意: 此处只表示出链表的结构示意图, 在状态组件(class 与 function)章节中会对上图的结构进行详细解读.

Hook 对象

react 高频对象中对Hook对象的属性做了说明, Hook对象具备.next属性, 所以Hook对象本身就是链表中的一个节点.

此外hook.queue.pending也构成了一个链表, 将hook链表与hook.queue.pending链表同时表示在图中, 得到的结构如下:

React 算法之链表操作 - 图4

注意: 此处只表示出链表的结构示意图, 在hook 原理章节中会对上图的结构进行详细解读.

链表合并

react中, 发起更新之后, 会通过链表合并的方式把等待(pending状态)更新的队列(updateQueue)合并到基础队列(class组件:fiber.updateQueue.firstBaseUpdate;function组件: hook.baseQueue), 最后通过遍历baseQueue筛选出优先级足够的update对象, 组合成最终的组件状态(state). 这个过程发生在reconciler阶段, 分别涉及到class组件和function组件.

具体场景:

  1. class组件中

    • class组件中调用setState, 会创建update对象并添加到fiber.updateQueue.shared.pending链式队列(源码地址).

      1. export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
      2. const updateQueue = fiber.updateQueue;
      3. // ...
      4. const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;
      5. // 将新的update对象添加到fiber.updateQueue.shared.pending链表上
      6. const pending = sharedQueue.pending;
      7. if (pending === null) {
      8. update.next = update;
      9. } else {
      10. update.next = pending.next;
      11. pending.next = update;
      12. }
      13. sharedQueue.pending = update;
      14. }

      由于fiber.updateQueue.shared.pending是一个环形链表, 所以fiber.updateQueue.shared.pending永远指向末尾元素(保证快速添加新元素)

      React 算法之链表操作 - 图5

    • fiber树构建阶段(或reconciler阶段), 会把fiber.updateQueue.shared.pending合并到fiber.updateQueue.firstBaseUpdate队列上(源码地址).

      1. export function processUpdateQueue<State>(
      2. workInProgress: Fiber,
      3. props: any,
      4. instance: any,
      5. renderLanes: Lanes,
      6. ): void {
      7. // This is always non-null on a ClassComponent or HostRoot
      8. const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);
      9. let firstBaseUpdate = queue.firstBaseUpdate;
      10. let lastBaseUpdate = queue.lastBaseUpdate;
      11. // Check if there are pending updates. If so, transfer them to the base queue.
      12. let pendingQueue = queue.shared.pending;
      13. if (pendingQueue !== null) {
      14. queue.shared.pending = null;
      15. // The pending queue is circular. Disconnect the pointer between first
      16. // and last so that it's non-circular.
      17. const lastPendingUpdate = pendingQueue;
      18. const firstPendingUpdate = lastPendingUpdate.next;
      19. lastPendingUpdate.next = null;
      20. // Append pending updates to base queue
      21. if (lastBaseUpdate === null) {
      22. firstBaseUpdate = firstPendingUpdate;
      23. } else {
      24. lastBaseUpdate.next = firstPendingUpdate;
      25. }
      26. lastBaseUpdate = lastPendingUpdate;
      27. }
      28. }

      React 算法之链表操作 - 图6

      React 算法之链表操作 - 图7

  2. function组件中

    • function组件中使用Hook对象(useState), 并改变Hook对象的值(内部会调用dispatchAction), 此时也会创建update(hook)对象并添加到hook.queue.pending链式队列(源码地址).
    • hook.queue.pending也是一个环形链表(与fiber.updateQueue.shared.pending的结构很相似)
      1. function dispatchAction<S, A>(
      2. fiber: Fiber,
      3. queue: UpdateQueue<S, A>,
      4. action: A,
      5. ) {
      6. // ... 省略部分代码
      7. const pending = queue.pending;
      8. if (pending === null) {
      9. // This is the first update. Create a circular list.
      10. update.next = update;
      11. } else {
      12. update.next = pending.next;
      13. pending.next = update;
      14. }
      15. queue.pending = update;
      16. }
    • fiber树构建阶段(或reconciler阶段), 会将hook.queue.pending合并到hook.baseQueue队列上(源码地址).
  1. ```js
  2. function updateReducer<S, I, A>(
  3. reducer: (S, A) => S,
  4. initialArg: I,
  5. init?: I => S,
  6. ): [S, Dispatch<A>] {
  7. // ... 省略部分代码
  8. if (pendingQueue !== null) {
  9. if (baseQueue !== null) {
  10. // 在这里进行队列的合并
  11. const baseFirst = baseQueue.next;
  12. const pendingFirst = pendingQueue.next;
  13. baseQueue.next = pendingFirst;
  14. pendingQueue.next = baseFirst;
  15. }
  16. current.baseQueue = baseQueue = pendingQueue;
  17. queue.pending = null;
  18. }
  19. }
  20. ```
  21. ![](/uploads/projects/react-illustration-series/snapshots/linkedlist/hook.baseQueue-merge-before.png)
  22. ![](/uploads/projects/react-illustration-series/snapshots/linkedlist/hook.baseQueue-merge-after.png)

总结

本节主要介绍了链表的概念和它在react源码中的使用情况. react中主要的数据结构都和链表有关, 使用非常高频. 源码中链表合并, 环形链表拆解, 链表遍历的代码篇幅很多, 所以深入理解链表的使用, 对理解react原理大有益处.

参考资料