【JavaScript-Day 30】ES6新特性:Set与Map,让你的数据管理更高效!

Langchain系列文章目录

01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南
02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖
03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南
04-玩转 LangChain:从文档加载到高效问答系统构建的全程实战
05-玩转 LangChain:深度评估问答系统的三种高效方法(示例生成、手动评估与LLM辅助评估)
06-从 0 到 1 掌握 LangChain Agents:自定义工具 + LLM 打造智能工作流!
07-【深度解析】从GPT-1到GPT-4:ChatGPT背后的核心原理全揭秘
08-【万字长文】MCP深度解析:打通AI与世界的“USB-C”,模型上下文协议原理、实践与未来

Python系列文章目录

PyTorch系列文章目录

机器学习系列文章目录

深度学习系列文章目录

Java系列文章目录

JavaScript系列文章目录

01-【JavaScript-Day 1】从零开始:全面了解 JavaScript 是什么、为什么学以及它与 Java 的区别
02-【JavaScript-Day 2】开启 JS 之旅:从浏览器控制台到 <script> 标签的 Hello World 实践
03-【JavaScript-Day 3】掌握JS语法规则:语句、分号、注释与大小写敏感详解
04-【JavaScript-Day 4】var 完全指南:掌握变量声明、作用域及提升
05-【JavaScript-Day 5】告别 var 陷阱:深入理解 letconst 的妙用
06-【JavaScript-Day 6】从零到精通:JavaScript 原始类型 String, Number, Boolean, Null, Undefined, Symbol, BigInt 详解
07-【JavaScript-Day 7】全面解析 Number 与 String:JS 数据核心操作指南
08-【JavaScript-Day 8】告别混淆:一文彻底搞懂 JavaScript 的 Boolean、null 和 undefined
09-【JavaScript-Day 9】从基础到进阶:掌握 JavaScript 核心运算符之算术与赋值篇
10-【JavaScript-Day 10】掌握代码决策核心:详解比较、逻辑与三元运算符
11-【JavaScript-Day 11】避坑指南!深入理解JavaScript隐式和显式类型转换
12-【JavaScript-Day 12】掌握程序流程:深入解析 if…else 条件语句
13-【JavaScript-Day 13】告别冗长if-else:精通switch语句,让代码清爽高效!
14-【JavaScript-Day 14】玩转 for 循环:从基础语法到遍历数组实战
15-【JavaScript-Day 15】深入解析 while 与 do…while 循环:满足条件的重复执行
16-【JavaScript-Day 16】函数探秘:代码复用的基石——声明、表达式与调用详解
17-【JavaScript-Day 17】函数的核心出口:深入解析 return 语句的奥秘
18-【JavaScript-Day 18】揭秘变量的“隐形边界”:深入理解全局与函数作用域
19-【JavaScript-Day 19】深入理解 JavaScript 作用域:块级、词法及 Hoisting 机制
20-【JavaScript-Day 20】揭秘函数的“记忆”:深入浅出理解闭包(Closure)
21-【JavaScript-Day 21】闭包实战:从模块化到内存管理,高级技巧全解析
22-【JavaScript-Day 22】告别 function 关键字?ES6 箭头函数 (=>) 深度解析
23-【JavaScript-Day 23】告别繁琐的参数处理:玩转 ES6 默认参数与剩余参数
24-【JavaScript-Day 24】从零到一,精通 JavaScript 对象:创建、访问与操作
25-【JavaScript-Day 25】深入探索:使用 for...in 循环遍历 JavaScript 对象属性
26-【JavaScript-Day 26】零基础掌握JavaScript数组:轻松理解创建、索引、长度和多维结构
27-【JavaScript-Day 27】玩转数组:push, pop, slice, splice 等方法详解与实战
28-【JavaScript-Day 28】告别繁琐循环:forEach, map, filter 数组遍历三剑客详解
29-【JavaScript-Day 29】数组迭代进阶:掌握 reduce、find、some 等高阶遍历方法
30-【JavaScript-Day 30】ES6新特性:Set与Map,让你的数据管理更高效!



前言

在 JavaScript ES6 标准之前,我们主要依赖数组(Array)和普通对象(Object)来存储和管理数据。数组用于存储有序的元素集合,而对象则用于存储键值对。然而,它们在某些特定场景下存在一些局限性。例如,普通对象的键只能是字符串或 Symbol 类型,且无法直接存储不重复的元素集合。为了弥补这些不足,ES6 引入了两种新的数据结构:SetMap。它们为处理特定类型的数据集合提供了更专业、更高效的解决方案。本篇文章将带你深入理解 SetMap 的特性、用法及其与传统数据结构的差异,助你提升代码质量与开发效率。


一、Set:独一无二的集合

Set 对象允许你存储任何类型的唯一值,无论是原始值还是对象引用。它类似于数学中的集合概念,集合中的元素都是唯一的,不会重复。

1.1 什么是 Set?

1.1.1 Set 的定义与特性

Set 是一种新的数据结构,它有以下核心特性:

  • 成员唯一性Set 中的成员的值都是唯一的,没有重复的值。如果你尝试添加一个已经存在的值,Set 不会做任何操作,也不会报错。
  • 无序性(但可迭代时按插入顺序):从概念上讲,集合是无序的。但在 JavaScript 的 Set 实现中,元素在迭代时会按照其插入顺序排列。
  • 任何类型的值Set 成员可以是任何数据类型,包括原始类型(数字、字符串、布尔等)和对象引用。

1.1.2 为何需要 Set?

在需要确保数据唯一性的场景下,Set 非常有用。例如:

  • 数组去重:快速去除数组中的重复元素。
  • 判断元素是否存在:高效地检查一个值是否已存在于某个集合中。
  • 存储唯一标识符列表:如用户 ID 列表,确保每个 ID 只出现一次。

1.2 Set 的创建与基本操作

1.2.1 创建 Set

你可以通过 new Set() 构造函数来创建一个空的 Set,或者传入一个可迭代对象(如数组)来初始化 Set

// 创建一个空的 Set
const mySet = new Set();
console.log(mySet); // Set(0) {}

// 通过数组初始化 Set (重复元素会被自动忽略)
const arr = [1, 2, 2, 3, 'hello', 'hello', true];
const uniqueSet = new Set(arr);
console.log(uniqueSet); // Set(5) { 1, 2, 3, 'hello', true }

1.2.2 核心方法

Set 实例提供了一些非常实用的方法来操作其成员:

(1) add(value):添加元素

Set 集合中添加一个新元素。如果元素已存在,则不进行任何操作。返回 Set 对象本身,因此可以链式调用。

const roles = new Set();
roles.add('admin');
roles.add('editor');
roles.add('admin'); // 尝试添加重复元素,无效

console.log(roles); // Set(2) { 'admin', 'editor' }
console.log(roles.add('viewer')); // Set(3) { 'admin', 'editor', 'viewer' } (返回 Set 对象)
(2) delete(value):删除元素

Set 集合中删除指定的元素。如果元素存在并被成功删除,则返回 true;否则返回 false

const permissions = new Set(['read', 'write', 'execute']);
const result1 = permissions.delete('write');
console.log(result1); // true
console.log(permissions); // Set(2) { 'read', 'execute' }

const result2 = permissions.delete('update'); // 删除一个不存在的元素
console.log(result2); // false
(3) has(value):判断元素是否存在

判断 Set 集合中是否包含指定的元素。如果包含,则返回 true;否则返回 false

const tags = new Set(['javascript', 'es6', 'web']);
console.log(tags.has('es6')); // true
console.log(tags.has('java')); // false
(4) clear():清空所有元素

移除 Set 对象内的所有元素。

const userIds = new Set([101, 102, 103]);
console.log(userIds.size); // 3
userIds.clear();
console.log(userIds.size); // 0
console.log(userIds); // Set(0) {}

1.2.3 size 属性

Set 实例拥有一个 size 属性,用于返回 Set 对象中值的数量(集合的基数)。

const numbers = new Set([1, 2, 3, 4, 5]);
console.log(numbers.size); // 5

1.2.4 遍历 Set

Set 对象是可迭代的,可以使用多种方式进行遍历:

(1) forEach() 方法

Set 实例的 forEach() 方法与数组的类似,但回调函数的参数略有不同:valuekey 都是元素本身(因为 Set 没有键的概念,为了与 MapforEach 接口保持一致),第三个参数是 Set 对象本身。

const colors = new Set(['red', 'green', 'blue']);
colors.forEach((value, key, set) => {
  console.log(`Value: ${value}, Key (same as value): ${key}`);
  // console.log(set === colors); // true
});
// 输出:
// Value: red, Key (same as value): red
// Value: green, Key (same as value): green
// Value: blue, Key (same as value): blue
(2) for...of 循环

这是遍历 Set 最简洁和推荐的方式。

const fruits = new Set(['apple', 'banana', 'orange']);
for (const fruit of fruits) {
  console.log(fruit);
}
// 输出:
// apple
// banana
// orange

Set 实例还有 keys()values()entries() 方法,它们都返回一个新的迭代器对象。对于 Set 而言:

  • keys() 返回一个包含集合中所有值的迭代器。
  • values() 返回一个包含集合中所有值的迭代器 (行为与 keys() 相同)。
  • entries() 返回一个包含 [value, value] 形式数组的迭代器,以模拟 Mapentries()
const letters = new Set(['a', 'b', 'c']);

console.log('Keys:');
for (const key of letters.keys()) {
  console.log(key); // a, b, c
}

console.log('\nValues:');
for (const value of letters.values()) {
  console.log(value); // a, b, c
}

console.log('\nEntries:');
for (const entry of letters.entries()) {
  console.log(entry); // ['a', 'a'], ['b', 'b'], ['c', 'c']
}

1.3 Set 的应用场景

1.3.1 数组去重

这是 Set 最经典的应用之一。

const duplicateArray = [1, 1, 2, 3, 4, 4, 4, 5, 'a', 'b', 'a'];
const uniqueArray = [...new Set(duplicateArray)]; // 使用扩展运算符将 Set 转回数组
console.log(uniqueArray); // [ 1, 2, 3, 4, 5, 'a', 'b' ]

1.3.2 判断元素是否存在

利用 has() 方法可以高效判断。

const allowedUsers = new Set(['Alice', 'Bob', 'Charlie']);
const currentUser = 'Bob';
if (allowedUsers.has(currentUser)) {
  console.log(`${currentUser} is allowed.`); // Bob is allowed.
} else {
  console.log(`${currentUser} is not allowed.`);
}

1.3.3 计算交集、并集、差集 (示例)

虽然 Set 本身没有直接提供这些数学运算的方法,但可以很容易地基于其特性实现。

const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);

// 并集
const union = new Set([...setA, ...setB]);
console.log('Union:', union); // Set(6) { 1, 2, 3, 4, 5, 6 }

// 交集
const intersection = new Set([...setA].filter(x => setB.has(x)));
console.log('Intersection:', intersection); // Set(2) { 3, 4 }

// 差集 (A - B)
const difference = new Set([...setA].filter(x => !setB.has(x)));
console.log('Difference (A-B):', difference); // Set(2) { 1, 2 }

二、Map:灵活的键值对

Map 对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者原始值)都可以作为一个键或一个值。这与普通对象(Object)有显著区别,普通对象的键只能是字符串或 Symbol

2.1 什么是 Map?

2.1.1 Map 的定义与特性

Map 是一种新的数据结构,它有以下核心特性:

  • 键的类型多样性Map 的键可以是任何类型的值,包括函数、对象或任何原始类型。而普通对象的键只能是字符串或 Symbol
  • 有序性Map 中的键值对是按照插入顺序排列的,迭代时会遵循这个顺序。
  • 键值对集合:存储的是键和值之间的映射关系。

2.1.2 为何需要 Map?

当需要使用非字符串类型的键,或者需要保持插入顺序的键值对集合时,Map 是更好的选择。

  • 使用对象作为键:例如,将 DOM 节点作为键来存储其相关元数据。
  • 保持插入顺序的字典:普通对象在 ES2015 之前不保证属性顺序。
  • 更安全的键名:避免与对象原型链上的属性名冲突。

下面是一个简单的 Mermaid 图,概念性地展示了 Map 结构:

graph LR
    subgraph MapInstance [Map: Key-Value Pairs]
        K1[Key1 (any type)] --> V1[Value1 (any type)]
        K2[Key2 (any type)] --> V2[Value2 (any type)]
        K3[Key3 (any type)] --> V3[Value3 (any type)]
        ObjKey{ "{id:1}" (Object) } --> OV["Associated Data"]
        FuncKey[ myFunc (Function) ] --> FV["Function Metadata"]
    end

2.2 Map 的创建与基本操作

2.2.1 创建 Map

你可以通过 new Map() 构造函数来创建一个空的 Map,或者传入一个包含键值对的数组(或任何可迭代对象,其元素是二元数组 [key, value])来初始化 Map

// 创建一个空的 Map
const myMap = new Map();
console.log(myMap); // Map(0) {}

// 通过二维数组初始化 Map
const initialPairs = [
  ['name', 'Alice'],
  [100, 'Score'],
  [true, 'Is Active'],
  [{id: 1}, 'User Object']
];
const userMap = new Map(initialPairs);
console.log(userMap);
// Map(4) {
//   'name' => 'Alice',
//   100 => 'Score',
//   true => 'Is Active',
//   { id: 1 } => 'User Object'
// }

2.2.2 核心方法

Map 实例提供了一系列方法来操作其键值对:

(1) set(key, value):设置键值对

Map 中添加或更新一个键值对。返回 Map 对象本身,因此可以链式调用。

const productMap = new Map();
productMap.set('id', 'P123');
productMap.set('name', 'Laptop');
productMap.set('price', 999);

const someObjectKey = { type: 'config' };
productMap.set(someObjectKey, { theme: 'dark', language: 'en' });

console.log(productMap.set('stock', 50)); // 返回 Map 对象
console.log(productMap);
// Map(4) {
//   'id' => 'P123',
//   'name' => 'Laptop',
//   'price' => 999,
//   { type: 'config' } => { theme: 'dark', language: 'en' },
//   'stock' => 50
// }
(2) get(key):获取值

根据键获取 Map 中对应的值。如果键不存在,则返回 undefined

console.log(productMap.get('name')); // 'Laptop'
console.log(productMap.get({ type: 'config' })); // undefined (因为是不同的对象引用)
console.log(productMap.get(someObjectKey)); // { theme: 'dark', language: 'en' } (相同的对象引用)
console.log(productMap.get('color')); // undefined (键不存在)

注意:当使用对象作为键时,Map 比较的是对象的引用。除非是同一个对象引用,否则即使两个对象结构相同,它们在 Map 中也被视为不同的键。

(3) has(key):判断键是否存在

判断 Map 中是否存在指定的键。如果存在,则返回 true;否则返回 false

console.log(productMap.has('price')); // true
console.log(productMap.has('description')); // false
console.log(productMap.has(someObjectKey)); // true
(4) delete(key):删除键值对

Map 中删除指定的键及其关联的值。如果键存在并被成功删除,则返回 true;否则返回 false

const result1 = productMap.delete('stock');
console.log(result1); // true
console.log(productMap.has('stock')); // false

const result2 = productMap.delete('nonExistentKey');
console.log(result2); // false
(5) clear():清空所有键值对

移除 Map 对象内的所有键值对。

console.log(productMap.size); // 3 (假设 stock 已被删除)
productMap.clear();
console.log(productMap.size); // 0
console.log(productMap); // Map(0) {}

2.2.3 size 属性

Map 实例的 size 属性返回 Map 对象中键值对的数量。

const dataMap = new Map([['a', 1], ['b', 2]]);
console.log(dataMap.size); // 2

2.2.4 遍历 Map

Map 对象是可迭代的,并且提供了多种遍历方式:

(1) forEach() 方法

Map 实例的 forEach() 方法回调函数的参数是 valuekeymap 对象本身。

const settings = new Map([
  ['theme', 'dark'],
  ['fontSize', 16],
  ['notifications', true]
]);

settings.forEach((value, key, map) => {
  console.log(`Key: ${key}, Value: ${value}`);
  // console.log(map === settings); // true
});
// 输出:
// Key: theme, Value: dark
// Key: fontSize, Value: 16
// Key: notifications, Value: true
(2) for...of 循环 (结合 entries(), keys(), values())
  • 遍历键值对 (entries): map.entries() 或直接 for...of map

    console.log('\nIterating over entries:');
    for (const entry of settings.entries()) { // settings.entries() 可以简写为 settings
      console.log(entry); // ['theme', 'dark'], ['fontSize', 16], ['notifications', true]
      console.log(`Key: ${entry[0]}, Value: ${entry[1]}`);
    }
    // 或者使用解构赋值
    console.log('\nIterating over entries with destructuring:');
    for (const [key, value] of settings) {
        console.log(`Key: ${key}, Value: ${value}`);
    }
    
  • 遍历键 (keys): map.keys()

    console.log('\nIterating over keys:');
    for (const key of settings.keys()) {
      console.log(key); // theme, fontSize, notifications
    }
    
  • 遍历值 (values): map.values()

    console.log('\nIterating over values:');
    for (const value of settings.values()) {
      console.log(value); // dark, 16, true
    }
    

2.3 Map 的应用场景

2.3.1 将对象作为键

当需要将 DOM 元素或其他对象与特定数据关联起来时,Map 非常有用。

const domElement1 = document.createElement('div');
const domElement2 = document.createElement('span');

const elementData = new Map();
elementData.set(domElement1, { id: 'elem1', eventCount: 0 });
elementData.set(domElement2, { id: 'elem2', eventCount: 5 });

console.log(elementData.get(domElement1)); // { id: 'elem1', eventCount: 0 }

// 假设有一个点击事件处理器
function handleClick(event) {
  const data = elementData.get(event.target);
  if (data) {
    data.eventCount++;
    console.log(`${data.id} clicked ${data.eventCount} times.`);
  }
}
// domElement1.addEventListener('click', handleClick); // 示例用法

2.3.2 存储配置或元数据

Map 可以清晰地存储具有不同类型键的配置信息。

const appConfig = new Map();
appConfig.set('version', '1.0.5');
appConfig.set('debugMode', true);
appConfig.set(Symbol('apiKey'), 'xyz123abc'); // 使用 Symbol 作为键
appConfig.set(window, { title: 'Main Window' }); // 使用 window 对象作为键

console.log(appConfig.get('version')); // '1.0.5'

三、Map/Set 与 Object/Array 的比较

理解 MapSet 与传统的 ObjectArray 之间的差异,有助于我们在合适的场景选择最恰当的数据结构。

3.1 Set 与 Array 的比较

特性SetArray
存储内容唯一的、无重复的值可以包含重复的值
键/索引无传统意义上的索引通过数字索引 (0, 1, 2…) 访问元素
顺序性迭代时按插入顺序元素按索引顺序排列
查找效率has() 方法通常比 Array.includes()indexOf() 更快 (尤其在大数据量时)includes(), indexOf() 效率相对较低
主要用途确保值的唯一性,快速判断成员是否存在存储有序的元素列表,常用于需要索引访问的场景
常用操作add(), delete(), has(), sizepush(), pop(), splice(), length

何时选择 Set 而不是 Array

  • 当你需要存储一系列不重复的值时。
  • 当你需要频繁检查一个值是否已存在于集合中时。
  • 当你需要进行集合运算(如并集、交集、差集)的基础时。

3.2 Map 与 Object 的比较

特性MapObject (Plain Object)
键的类型任何类型的值 (原始类型、对象、函数等)只能是字符串 (String) 或符号 (Symbol)
顺序性键值对按插入顺序迭代在 ES2015 之前,属性顺序不保证;ES2015+ 对某些情况有规定,但仍不如 Map 明确
大小获取size 属性,直接且高效通常通过 Object.keys(obj).length 获取,间接
迭代直接可迭代 (for...of),提供 keys(), values(), entries()需要 for...in (会遍历原型链属性,需 hasOwnProperty 过滤) 或 Object.keys(), Object.values(), Object.entries()
原型干净,不继承任何属性,除非显式从 Map.prototype 继承继承自 Object.prototype (除非用 Object.create(null)),可能导致键名冲突
性能在频繁增删键值对的场景下,通常比 Object 表现更好对于固定数量的属性,访问速度可能非常快

何时选择 Map 而不是 Object

  • 当你需要使用非字符串/Symbol 类型的键时(例如,用 DOM 元素作为键)。
  • 当你需要严格保证键值对的插入顺序时。
  • 当键值对数量会频繁变动,且需要高效地获取大小时。
  • 当你希望避免原型链带来的潜在键名冲突时。

3.3 何时选择?(决策流程图思路)

下面是一个简化的决策思路,帮助你选择合适的数据结构,可以用 Mermaid 流程图来形象化表达:

否, 且键主要是简单字符串
开始: 我需要存储什么样的数据?
是一系列独立的项吗?
这些项需要保证唯一吗?
使用 Set
使用 Array
是键值对结构吗?
键的类型会超出字符串或Symbol吗?
使用 Map
是否严格需要插入顺序或者频繁增删查?
可以考虑 Object
可能需要其他数据结构或组合

这个流程图提供了一个大致的思考方向。实际开发中,选择还需结合具体业务需求、性能考量和团队编码习惯。


四、总结

SetMap 是 ES6 中引入的强大且实用的数据结构,它们为 JavaScript 开发者提供了更丰富的工具来处理不同类型的数据集合和映射关系。

  • Set 的核心价值在于其成员的唯一性:

    1. 它非常适合用于数组去重、判断成员是否存在等场景。
    2. 提供 add(), delete(), has(), clear() 等简洁的 API 和 size 属性。
    3. 可直接使用 for...offorEach() 进行迭代。
  • Map 的核心价值在于其键的灵活性和有序性:

    1. 它允许任何类型的值作为键,弥补了普通对象键类型的限制。
    2. 键值对按插入顺序存储和迭代。
    3. 提供 set(), get(), has(), delete(), clear() 等 API 和 size 属性。
    4. 同样支持 for...offorEach() 迭代,并提供 keys(), values(), entries() 方法。
  • 选择合适的结构:

    1. 当处理唯一值集合时,优先考虑 Set
    2. 当需要灵活的键类型或有序的键值对时,优先考虑 Map
    3. 传统的 ArrayObject 仍然是许多场景下的首选,特别是当 Array 的索引访问和 Object 的简单字符串键特性符合需求时。

掌握 SetMap 的使用,能够让我们编写出更简洁、更高效、更易于维护的 JavaScript 代码。在日常开发中,根据具体需求选择最合适的数据结构,是提升代码质量的关键一步。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吴师兄大模型

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值