Vue3 中的双向链表依赖管理详解与示例

在 Vue3 的响应式系统中,双向链表是一个非常重要的数据结构。相比 Vue2 使用数组来存放依赖,Vue3 选择链表的原因在于效率更高,尤其是在频繁收集和清理依赖时,链表可以显著优化性能。本文将通过讲解和示例代码,帮助你理解这一点。


为什么要用双向链表

在响应式依赖收集过程中,Vue 需要完成两件事:

  1. 收集依赖 :当访问响应式数据时,要记录当前副作用函数(effect)。
  2. 清理依赖:当副作用函数重新运行或失效时,需要把它从依赖集合里移除。

如果依赖集合使用数组:

  • 删除某个依赖需要遍历整个数组才能找到它,复杂度是 O(n)。
  • 当依赖数量多、更新频繁时,性能就会下降。

使用双向链表:

  • 删除节点只需要修改前后两个指针,复杂度是 O(1)。
  • 插入节点同样是 O(1)。
  • 非常适合依赖频繁增删的场景。

双向链表的基本结构

每个链表节点包含:

  • effect:保存副作用函数
  • prev:指向前一个节点
  • next:指向后一个节点

通过 prevnext,节点可以快速被插入或删除。

示例:

css 复制代码
A <-> B <-> C
删除 B:
A.next = C
C.prev = A

代码示例

下面的示例展示了如何通过双向链表来管理依赖的添加、删除和触发。

kotlin 复制代码
// 链表节点:存储副作用函数和前后指针
class EffectNode {
  constructor(effect) {
    this.effect = effect; // 要执行的副作用函数
    this.prev = null;     // 前一个节点
    this.next = null;     // 后一个节点
    this.dep = null;      // 当前节点所属的依赖集合
  }
}

// 依赖集合(Dep):使用双向链表存储多个 effect
class Dep {
  constructor() {
    this.head = null; // 链表头
    this.tail = null; // 链表尾
  }

  // 添加依赖:O(1),插入到链表尾部
  add(effect) {
    const node = new EffectNode(effect);
    node.dep = this;
    if (!this.head) {
      // 如果链表为空,头尾都指向当前节点
      this.head = this.tail = node;
    } else {
      // 否则插入到尾部
      this.tail.next = node;
      node.prev = this.tail;
      this.tail = node;
    }
    return node; // 返回节点,方便以后删除
  }

  // 删除依赖:O(1),只需修改相邻节点指针
  remove(node) {
    if (node.prev) node.prev.next = node.next;
    if (node.next) node.next.prev = node.prev;
    if (node === this.head) this.head = node.next;
    if (node === this.tail) this.tail = node.prev;
    // 清理引用,帮助垃圾回收
    node.prev = node.next = node.dep = null;
  }

  // 触发依赖:遍历链表,执行所有副作用函数
  trigger() {
    let cur = this.head;
    while (cur) {
      cur.effect();
      cur = cur.next;
    }
  }
}

// 模拟响应式数据
const state = { count: 0 };
// 每个属性都可能有一个依赖集合,这里用一个 dep 演示
const dep = new Dep();

// 注册副作用函数,并收集依赖
function effect(fn) {
  const runner = () => fn();
  const node = dep.add(runner);
  runner(); // 立即执行一次
  return node; // 返回节点,方便后续删除
}

// 使用:收集依赖
const node = effect(() => {
  console.log("副作用执行: count =", state.count);
});

// 修改数据时触发依赖
state.count++;
dep.trigger(); // 输出: 副作用执行: count = 1

// 删除依赖,再次触发时不会执行
dep.remove(node);
state.count++;
dep.trigger(); // 无输出

输出结果

ini 复制代码
副作用执行: count = 0
副作用执行: count = 1

在移除依赖后,再次修改数据时不会有输出,说明链表删除操作成功。


总结

  • Vue3 使用双向链表来存储依赖,是为了提升收集和清理的效率。
  • 相比数组,链表的插入和删除复杂度更低,尤其适合依赖数量多且频繁变化的场景。
  • 通过示例可以看到,依赖的收集、触发和清理都能高效完成。

这就是 Vue3 响应式系统比 Vue2 更快的一个重要原因。

本文部分内容借助 AI 辅助生成,并由作者整理审核。

相关推荐
૮・ﻌ・6 分钟前
Vue2(一):创建实例、插值表达式、Vue响应式特性、Vue指令、指令修饰符、计算属性
前端·javascript·vue.js
半生过往1 小时前
2025 前端动效实战指南:Vue Bits & React Bits 深度拆解(功能 / 复用 / 高频问题处理)
前端·vue.js·react.js
程序员包打听1 小时前
Vitest 4.0 重磅发布:Browser Mode 正式稳定,前端测试进入新纪元
前端
BumBle1 小时前
UniApp 多页面编译优化:编译时间从10分钟到1分钟
前端
星链引擎1 小时前
大语言模型的技术突破与稳定 API 生态的构建
前端
还是大剑师兰特1 小时前
TypeScript 面试题及详细答案 100题 (71-80)-- 模块与命名空间
前端·javascript·typescript
BumBle1 小时前
使用 SortableJS 实现vue3 + Element Plus 表格拖拽排序
前端·vue.js·element
玉宇夕落1 小时前
HTML5 音乐敲击乐静态界面
前端
海在掘金611271 小时前
告别"拼写错误":TS如何让你的代码"字字精准"
前端
用户47949283569151 小时前
什么是XSS攻击,怎么预防,一篇文章带你搞清楚
前端·javascript·安全