JavaScript中Array、Set与Map的数据结构特性对比

作者:木棉

时间:2026-10-30

浏览:281

深入对比JavaScript中Array、Set与Map的数据结构特性、底层存储模型、操作时间复杂度及实际业务选型方案,帮助开发者准确选择最合适的数据集合类型。

文章目录

在现代JavaScript(ECMAScript 2015/ES6及后续规范)中,Array、Set和Map构成了最核心的数据集合体系。Array是基于连续整数索引的有序线性列表,主要用于维护元素的排列顺序和执行批量转换;Set是基于值判等的唯一集合,专门用于处理去重和高速成员测试;Map则是任意键类型的哈希映射表,解决了传统对象键名仅限字符串/Symbol且原型受污染的问题。选择合适的数据集合不仅直接决定代码的可读性,更对高频增删查改场景下的运行性能具有决定性影响。

一、底层数据组织与存储模型对比

JavaScript引擎(如V8)对这三种数据结构采用了不同的内部存储和检索设计:

  • Array(数组):逻辑上以整数索引(0到2^32-2)连续排列。V8内部会根据元素类型及密度将其划分为快数组(连续内存缓冲区)与慢数组(字典哈希表)。通过数组下标读取元素是直接的内存偏移访问,速度极高;但如果通过元素值查找索引或执行中间插入/删除,则需要逐项扫描或移动后续元素。
  • Set(集合):内部采用哈希表结构存储。Set中没有传统意义上的键值分离,其“值”本身即作为哈希检索的关键依据。引擎通过SameValueZero算法保证内部元素的绝对唯一性,使has()方法能够在近似O(1)的平均时间内完成查找。
  • Map(映射字典):由哈希桶配合双向链表实现。哈希桶负责提供O(1)的键查找性能,双向链表则负责精确记录键值对的插入顺序。Map允许任何JavaScript实体(包括原始类型、DOM节点、对象及函数)作为键。
JavaScript中Array、Set与Map的数据结构特性对比
图1:Array、Set与Map的底层数据组织及存储机制对比

二、键与值的约束及判等机制

在存储元素时,三种结构在“键类型”与“元素判等规则”上有着本质不同:

1. 键与值的类型约束

  • Array:键为整数下标,实际访问时若传入数字字符串会被自动转换为数字;值支持任何类型,允许多次存放相同的值。
  • Set:只有值(无独立键),允许存入任何数据类型。但同一集合内不可存在重复元素。
  • Map:键与值均支持任意JavaScript类型。与字面量对象 { } 仅允许字符串和Symbol作为键不同,Map可以将DOM元素、函数、类实例或普通对象直接作为Key。

2. 判等规则(SameValueZero)

Set在判断元素是否重复、以及Map在判断键是否存在时,使用的是规范定义的 SameValueZero 算法,其核心规则如下:

  • +0 与 -0 被认定为相等。
  • NaN 与 NaN 被认定为相等(而在常规运算符中 NaN === NaN 返回 false)。
  • 引用类型(对象、数组、函数)依据内存地址引用判等。即使两个对象内容完全相同,若引用不同,仍被视为独立的元素或键。
// 引用类型在 Set 和 Map 中的判等表现
const mySet = new Set();
mySet.add({ id: 1 });
mySet.add({ id: 1 });
console.log(mySet.size); // 输出 2!因为两个字面量对象指向不同堆内存地址

const sharedKey = { id: 1 };
const myMap = new Map();
myMap.set(sharedKey, "配置A");
console.log(myMap.get({ id: 1 })); // 输出 undefined(新对象的引用不同)
console.log(myMap.get(sharedKey)); // 输出 "配置A"(引用相同)

三、核心操作性能与时间复杂度对比

在性能敏感的场景中,不同数据结构的平均时间复杂度差异极为显著:

JavaScript中Array、Set与Map的数据结构特性对比
图2:Array、Set与Map常见CRUD操作时间复杂度及性能特征对比

在海量数据(例如10万条记录)的场景下:

  • 使用 array.includes(x) 进行存在性检查,当在循环内部嵌套执行时,时间复杂度会恶化为 O(n²),往往直接导致界面卡顿;
  • 将数组转为 new Set(array) 后使用 set.has(x) 进行判断,单次查找耗时恒定为 O(1),整体检索性能通常能提升百倍以上。

四、数据结构选型推导路径与典型场景

在实际编码中,应当根据数据的形态(是否键值对)、约束条件(是否需要唯一性)、访问模式(按索引还是按值)快速做出选型决策:

JavaScript中Array、Set与Map的数据结构特性对比
图3:根据业务场景与数据特征选型决策推导路径

五、开发实践中的关键注意事项与陷阱

1. JSON 序列化陷阱

JSON.stringify() 仅对原生 Array 和普通字面量对象提供开箱即用的序列化支持。如果将 Set 或 Map 直接传入 JSON.stringify(),默认会被序列化为 "{}",导致数据静默丢失:

const userMap = new Map([["name", "Alice"], ["role", "Admin"]]);
console.log(JSON.stringify(userMap)); // 输出 "{}"(非预期!)

// 正确序列化与还原做法:
// 1. 转为 Object(要求键为字符串/Symbol)
const jsonStr = JSON.stringify(Object.fromEntries(userMap));
const restoredMap = new Map(Object.entries(JSON.parse(jsonStr)));

// 2. 转为二维数组(支持任意键类型)
const jsonArrStr = JSON.stringify(Array.from(userMap.entries()));
const fullyRestoredMap = new Map(JSON.parse(jsonArrArrStr));

2. 集合与数组的相互转换技巧

利用展开语法(Spread Operator ...)或 Array.from(),可以非常便捷地在数组与Set之间架起桥梁:

// 原始数组去重
const originalList = [1, 2, 2, 3, 4, 4, 5];
const uniqueList = [...new Set(originalList)]; // [1, 2, 3, 4, 5]

// 两个集合求交集(ES2024亦已支持原生的 setA.intersection(setB))
const setA = new Set([1, 2, 3]);
const setB = new Set([2, 3, 4]);
const intersection = new Set([...setA].filter(x => setB.has(x))); // Set { 2, 3 }
内存泄漏风险提示:

Map 和 Set 对作为键或值的对象持有强引用。即使外部变量已经解除引用(如 element = null),只要它仍存在于 Map 中,垃圾回收器(GC)就无法释放该对象占用的堆内存。如果在 DOM 节点绑定数据或短生命周期缓存场景中,应优先考虑使用 WeakMap 或 WeakSet,以便对象被垃圾回收时自动同步清理对应映射。

六、全维度特性总结矩阵

通过以下综合特性矩阵,可以系统梳理这三种数据结构在语法规范与实际应用中的全部核心特征:

JavaScript中Array、Set与Map的数据结构特性对比
图4:JavaScript 中 Array、Set 与 Map 的全维度特性速查矩阵

综上所述,Array、Set 和 Map 并非互相替代的关系,而是各有侧重的专业工具:当关注数据的线性排列、需要频繁通过下标访问或执行数组变换算子时,首选 Array;当首要目标是保证元素的唯一性、去重或高频校验包含状态时,首选 Set;而在需要构建键值映射关系、应对动态高频增删或需要非字符串键名时,Map 则是性能更优且更纯净的解决方案。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章