js 数据结构

目录

前言

本文主要讲的是数据结构,不讲对应数据结构的算法。

相关数据结构的算法实现请看:前端视角下的算法(algorithm)

一、前端数据结构

前端数据结构 ≠ 仅仅是 JavaScript 语言层面的数据结构,但:前端 90% 的数据结构问题,确实是用 JavaScript 数据结构来承载和实现的。

前端数据结构主要是 JS 数据结构,但绝不只限于 JS 数据结构。

层级 是否属于前端数据结构 具体是什么 举例 是否基于 JS
JS 语言层 ✅ 是 Array / Map / Set / Object 列表、缓存
抽象数据结构 ✅ 是 栈 / 队列 / 树 / 图 / 堆 撤销、关系图 ✅(用 JS 实现)
框架内部结构 ✅ 是 VDOM、Fiber、依赖图 React / Vue ❌(抽象层)
浏览器内部结构 ⚠️ 间接相关 DOM Tree、Render Tree 页面渲染
协议 / 网络结构 ❌ 不是 HTTP 报文结构 请求响应
数据模型设计 ✅ 是 Schema / DSL / JSON 结构 表单、低代码

二、主流前端框架所采用的主要数据结构对比

前端框架的差异,本质不是 API,而是------"用什么数据结构,解决什么规模的问题。"

  • React:用链表 + 优先队列解决大规模 UI 调度
  • Vue:用树 + 图解决响应式开发体验
  • Solid:直接用图,跳过虚拟 DOM
  • Svelte:把数据结构提前到编译期

若不理解数据结构,就只能"会用 API,不会设计系统"。

1、主流前端框架核心数据结构对比总表

框架 虚拟 DOM 结构 更新 / Diff 结构 组件组织结构 状态存储结构 依赖收集 / 响应式 调度 / 任务结构 路由结构 关键说明(本质)
React 树(Virtual DOM Tree) 链表(Fiber) 树 + 链表 Map / Object Map + Set 优先队列(Lane) 用链表打断递归,实现可中断渲染
Vue 2 树 Diff Object 依赖图(Graph) 队列 Object + getter/setter
Vue 3 树 Diff Map / Object 图(Graph)+ Map + Set 队列 Proxy + 精准依赖收集
Angular 树 Diff RxJS(流) 观察者图 任务流 强依赖响应式流
Svelte ❌ 无 VDOM 编译期生成 静态结构 变量 编译期依赖图 编译期 数据结构在编译期展开
Next.js React 同 React 同 Map React 同 React 同 文件系统树 路由即文件树
Nuxt Vue 同 Vue 同 Map Vue 同 Vue 同 文件系统树 Vue + SSR 封装
Solid.js 依赖图 Signal Map 细粒度图 调度队列 图优先于树

2、核心模块「拆解级」数据结构对照(重点)

(1)、虚拟 DOM & 组件结构

框架 本质数据结构
React 树 + 单向链表(Fiber)
Vue
Angular
Svelte 编译期静态结构
Solid 依赖图(Graph)

只有 React 把"树"拆成了"链表"

(2)、响应式 / 状态追踪(最容易被忽略)

框架 核心结构
Vue 2 图(Watcher 依赖)
Vue 3 图 + Map + Set
React Map(Hook 索引)
Solid 细粒度依赖图
Angular Observable 图

Vue / Solid 本质是图系统

(3)、更新调度 / 优先级(高阶)

框架 使用的数据结构
React 优先队列(Lane)+ 链表
Vue 队列
Angular 事件流
Solid 调度队列

只有 React 有"类操作系统级调度"

(4)、从「数据结构角度」看框架设计哲学

框架 核心偏好数据结构 设计哲学
React 链表 / 队列 / 堆 可中断、可调度
Vue 树 / 图 易用、直观
Angular 流 / 图 企业级、强约束
Svelte 编译期结构 运行时极简
Solid 极致性能

三、JavaScript 数据结构「总览」

1、js 数据结构「原生 & 非原生」

JavaScript 只有 4 种原生数据结构类型:

  • Array
  • Object
  • Map
  • Set

其余所有:

  • 队列
  • 链表
  • LRU Cache

全部都是"抽象数据结构(ADT)",用 JS 原生结构实现。

2、js 数据结构大全「对比」(⭐️⭐️⭐️⭐️⭐️)

数据结构 中文名称 是否 JS 原生 核心特性 JS 常见实现 时间复杂度(核心) 空间复杂度 前端典型应用场景 重点 / 易错点
Array 数组 连续内存、下标访问 [] 访问 O(1),插入尾 O(1),插入头 O(n) O(n) 列表、表格、虚拟列表 shift/unshift 性能差
Stack 后进先出(LIFO) Array.push / pop 入栈 O(1),出栈 O(1) O(n) 撤销重做、路由回退、DFS 栈只是抽象结构
Queue 队列 先进先出(FIFO) 双指针 / 对象 入队 O(1),出队 O(1) O(n) BFS、任务调度、消息队列 不推荐用 shift()
Deque 双端队列 两端可进出 自实现 入队 O(1),出队 O(1) O(n) 滑动窗口、调度系统 JS 无原生支持
Linked List 链表 非连续内存 节点对象 {value, next} 查找 O(n),插入 O(1) O(n) LRU Cache、频繁插删 不支持随机访问
Object 对象 哈希键值对 {} 查找 O(1) O(n) 配置、低频 Map key 仅 string/symbol
Map 映射 真正哈希表 new Map() 查找 O(1) O(n) 状态管理、缓存 推荐替代 Object
Set 集合 自动去重 new Set() 查找 O(1) O(n) 去重、visited 集合 不存 key-value
Tree 层级结构 递归对象 遍历 O(n) O(n) 菜单、权限、DOM、路由 DFS/BFS 必会
Binary Tree 二叉树 每节点 ≤2 子节点 节点对象 {value, left, right} 平衡树查找 O(log n),最坏 O(n) O(n) 搜索、排序 是否平衡关键
Heap 完全二叉树 数组模拟 插入 O(log n),删除 O(log n) O(n) Top K、优先队列 JS 无原生堆
Graph 点 + 边 邻接表/邻接矩阵 遍历 O(V+E) O(V+E) 关系图、知识图谱 防止死循环
Trie 字典树 前缀共享 嵌套对象 查找 O(k) O(总字符数) 搜索提示、关键词 空间换时间
Hash Table 哈希表 ⚠️ 间接 key → value Map / Object 查找 O(1) O(n) 几乎所有系统 冲突由 JS 引擎处理
LRU Cache 最近最少使用 淘汰策略 Map + 双向链表 get/put O(1) O(n) 缓存淘汰 高频面试题
Priority Queue 优先队列 按权重出队 插入/删除 O(log n) O(n) 调度系统、Top K ≠ 普通队列

四、JavaScript 数据结构「逐个击破」

从数据结构的角度看前端(一种另类的视角):前端 = Array + Map + Tree + Graph

  • Array → 渲染
  • Map → 状态 & 性能
  • Tree → 结构
  • Graph → 关系

1、线性数据结构(从最基础开始)

(1)数组(Array)------一切的起点(⭐)

数组是用连续内存空间存储元素的一种线性数据结构,通过索引快速访问每个元素(数组 = 线性关系 + 连续内存 + 索引访问)。

特点:

  • 连续内存存储
  • 顺序线性
  • 固定 / 动态长度(可扩展性)

为什么快?

  • arr[1000] → 直接定位内存
  • 访问 O(1)

JS 中的核心操作:

typescript 复制代码
const arr = [1, 2, 3]

arr.push(4)      // 尾部插入 O(1)
arr.pop()        // 尾部删除 O(1)

arr.unshift(0)   // 头部插入 O(n)
arr.shift()      // 头部删除 O(n)

时间复杂度:

操作 复杂度
随机访问 O(1)
尾部插入/删除 O(1)
中间/头部插入 O(n)

前端真实用途:

  • 列表 / 表格
  • 虚拟列表
  • Diff 前后的节点集合

❌ 致命坑:

  • 大量使用 shift / unshift
  • 在大数组里频繁 splice

(2)、栈(Stack)------后进先出(LIFO)(⭐)

特点:

  • 内存连续
  • 大小固定
  • 访问快:入栈和出栈的时间复杂度都是 O(1)

JS 是否原生?

  • ❌ 不是类型
  • ✅ 行为可用 Array 表达

JS 实现:

typescript 复制代码
const stack = []
stack.push(1)
stack.push(2)
stack.pop() // 2

前端真实用途:

  • 撤销 / 重做
  • 路由历史
  • DFS(Depth-First Search,即:深度优先搜索)
  • 调用栈(JS 引擎)

关键认知:

  • 递归 = 系统栈
  • JS 基本类型存栈,引用类型存堆(栈存指针) 。访问基本类型快,引用类型灵活可变。

(3)、队列(Queue)------先进先出(FIFO)(⭐)

特点:

  • 线性结构:队列是一个线性列表,元素之间有前后关系
  • 受限访问:只能从队尾入队,队头出队,不允许随机访问中间元素
  • 先进先出(FIFO):保证数据处理顺序,适合排队、任务调度、缓冲区等场景
  • 动态可扩展:队列大小可根据数据增长而变化(数组 + 链表都可实现)
  • 可用于抽象概念:广度优先搜索(BFS)、消息队列、打印任务调度等

错误实现(常见):

typescript 复制代码
arr.push(x)
arr.shift() // ❌ O(n)

正确实现(双指针):

typescript 复制代码
class Queue {
  constructor() {
    this.data = {}
    this.head = 0
    this.tail = 0
  }
  enqueue(x) {
    this.data[this.tail++] = x
  }
  dequeue() {
    const val = this.data[this.head]
    delete this.data[this.head++]
    return val
  }
}

前端用途:

  • BFS(Breadth-First Search,即:广度优先搜索)
  • 任务调度
  • 消息队列
  • Vue / React 更新队列

(4)、双端队列(Deque)(⭐)

虽然双向队列与普通队列都属于线性数据结构,但二者存在本质差异:

结构 访问规则 插入/删除位置
队列(Queue) FIFO(先进先出) 只能从 队尾入队 ,从 队头出队
双向队列(Deque) 双端可操作 队头/队尾都可入队或出队

双向队列是队列的拓展,增加了双端操作能力,本质依旧是线性结构,但操作更灵活,可同时支持两端的插入和删除。

特点:

  • 双端操作(头尾都可进出)
  • 线性顺序
  • 支持 FIFO / LIFO
  • 高效操作(O(1))

应用:

应用场景 为什么用双向队列
滑动窗口问题 需要头尾快速入出
LRU 缓存 最近访问元素移动到头/尾
双端任务调度 高优先任务可从头插入

2、链表(突破数组局限)(⭐)

链表是用节点 + 指针来组织数据的线性关系结构,每个节点指向下一个节点,实现动态存储和顺序访问(链表 = 线性关系 + 指针连接 + 动态内存分配)。

特点:

  • 节点动态存储(内存非连续)
  • 顺序访问(线性关系)
  • 灵活扩展 & 可变长度

链表虽然也是线性数据结构,但它和数组的"线性"有本质上的区别:

  • 数组:线性顺序依赖索引(强调顺序位置)
  • 链表:线性顺序依赖指针连接(强调节点连接)
特性 数组 链表
内存 连续 非连续
访问方式 随机访问 O(1) 顺序访问 O(n)
插入 / 删除 中间元素 O(n) 已知节点 O(1)
线性关系体现 通过索引 通过指针连接

JS 实现链表:

typescript 复制代码
class Node {
  constructor(val) {
    this.val = val
    this.next = null
  }
}

链表复杂度:

  • 查找 O(n)
  • 插入 O(1)

前端用途:

  • LRU Cache(Least Recently Used Cache,即:最近最少使用缓存)
  • React Fiber(react 内部的可中断渲染架构)
  • 频繁插删结构

❌ 最大缺点:

  • 不支持随机访问

3、哈希结构(性能核心)

哈希表是用"确定性映射规则",把"任意 key"直接映射到"存储位置"的一种结构(哈希表 = 映射关系(Key → Value)的 O(1) 近似实现)。

特点:

  • 近似 O(1) 的增删改查
  • 无序(或弱序)
  • 空间换时间

为什么很多人会误以为 Array 是简单的哈希表呢?

因为 arr[0] 的下标也是"key",这看起来像哈希表的 key → value 结构。但其实 Array 的 核心语义是"有序索引表",索引是 连续的非负整数,底层有 "连续内存 / 稀疏数组 / elements kind" 优化。Array 的性能优化是围绕「顺序访问」设计的,不是哈希。所以:

❌ Array ≠ Hash Table

✅ Array = 顺序表(Indexed List)

结论:JS 里只有 Object 和 Map 可以"看作"基于哈希的结构;Array 是顺序表,不能看作哈希表

(1)、Object ------ 老牌键值对(⭐)

本质:

  • 哈希表
  • key 自动转字符串

缺点:

  • 原型污染
  • key 限制
  • 顺序不可靠

使用场景:

  • 简单配置对象

(2)、Map ------ 真正的哈希表(⭐)

为什么需要 Map?

因为 Object 不适合高频哈希操作

特点:

  • key 可以是任意类型
  • 有 size
  • 保证插入顺序
typescript 复制代码
const map = new Map()
map.set(obj, 123)

前端用途:

  • 状态管理
  • 缓存
  • 依赖映射

(3)、Set ------ 去重神器(⭐)

typescript 复制代码
const set = new Set([1,1,2])

用途:

  • 去重
  • visited 集合(用来记录"已经访问过的节点",防止重复访问或死循环)
  • 防止死循环

4、树结构(前端的骨架)

(1)、树(Tree)(⭐)

树是用"父子层级关系"来组织数据的一种"单源分治结构"(树 = 层级 + 分治 + 约束的图)。

特点:

  • 层级清晰
  • 递归友好
  • 局部隔离

遍历(必须会):

  • DFS(深度优先搜索):
typescript 复制代码
function dfs(node) {
  if (!node) return
  node.children.forEach(dfs)
}
  • BFS(广度优先搜索):
typescript 复制代码
function bfs(root) {
  const queue = [root]
  while (queue.length) {
    const node = queue.shift()
    queue.push(...node.children)
  }
}

前端用途:

  • 菜单
  • 权限
  • DOM
  • 路由
  • 虚拟 DOM

(2)、二叉树 / 平衡树(了解)

二叉树:每个节点最多有两个子节点。

用途:

  • 搜索
  • 排序

(3)、完全二叉树(了解)

完全二叉树:

  • 除了最后一层外,其他各层的节点数都达到最大;
  • 最后一层的节点 从左到右连续排列,中间不允许有空缺。

5、堆(⭐)

堆是为了"快速找到极值(最大 / 最小)"而对完全二叉树施加的一种"局部有序约束结构"

堆主要可分为两种类型:

  • 小顶堆 (min heap):任意节点的值 <= 其子节点的值。
  • 大顶堆 (max heap):任意节点的值 >= 其子节点的值。

特点:

  • 完全二叉树(结构约束)
  • 局部有序(堆序性)
    • 大顶堆:父 ≥ 子
    • 小顶堆:父 ≤ 子
  • 操作稳定在 O(log n)

用途:

  • Top K(从一组数据中,找出"最大或最小的 K 个元素")
  • 调度系统(顺序、优先级、时间、中断)
  • React Lane(思想):React 用来给更新任务标记"优先级"的概念,本质是一个"位掩码优先级队列",用于调度 Fiber 渲染顺序。

关键认知:

JS 基本类型存栈,引用类型存堆(栈存指针) 。访问基本类型快,引用类型灵活可变。

6、图结构(Graph)(⭐)

图(Graph)是用"点 + 边"来描述"多对多的关系网络"

特点:

  • 非线性
  • 多对多关系
  • 可形成"环"

JS 表达:

typescript 复制代码
const graph = {
  A: ['B', 'C'],
  B: ['D']
}

遍历:

  • DFS(深度优先搜索)
  • BFS(广度优先搜索)
  • visited(Set)(记录"已经访问过的节点或元素"的集合,防止重复访问或死循环)

前端用途:

  • 人物关系
  • 知识图谱
  • 依赖系统
  • Graphin / G6 / relation-graph

7、组合型结构(工程必会)

(1)、LRU Cache(最近最少使用缓存)(⭐️)

LRU Cache(最近最少使用缓存)是在"容量受限"的前提下,优先保留"最近被访问"的数据的一种淘汰策略结构(LRU = 时间局部性假设 + O(1) 访问的结构组合)。

本质:Map + 双向链表。

特点:

  • 所有核心操作都是 O(1)
  • 时间局部性驱动
  • 空间受限 + 自动淘汰

用途:

  • 缓存淘汰
  • 浏览器缓存思想
相关推荐
Sheep Shaun4 小时前
STL:list,stack和queue
数据结构·c++·算法·链表·list
zore_c4 小时前
【数据结构】二叉树初阶——超详解!!!(包含二叉树的实现)
c语言·开发语言·数据结构·经验分享·笔记·算法·链表
鹿角片ljp20 小时前
力扣 83: 删除排序链表中的重复元素(Java实现)
java·leetcode·链表
tgethe1 天前
Java 链表(LinkedList)
java·开发语言·链表
Bdygsl1 天前
数据结构 —— 顺序表
数据结构·链表
2301_789015621 天前
每日精讲:环形链表、两个数组中的交集、随机链表的复制
c语言·数据结构·c++·算法·leetcode·链表·排序算法
SadSunset2 天前
力扣题目142. 环形链表 II的解法分享,附图解
算法·leetcode·链表
25Qi导航2 天前
专业期刊发表公司
链表
Dylan的码园2 天前
队列与queue
java·数据结构·链表