各位观众,掌声欢迎!我是今天的主讲人,人称“代码界的段子手”(其实是自己封的)。今天咱们不讲枯燥的理论,来聊聊JavaScript里两个好玩儿的数据结构:Set和Map。
先别皱眉头,我知道你们可能觉得数组和对象已经够用了,干嘛还要学这些“花里胡哨”的东西?但相信我,学完之后你会发现,它们就像你工具箱里的瑞士军刀,关键时刻能帮你解决很多麻烦。
一、Set:不允许重复元素的集合,专注“唯一”
你可以把Set想象成一个非常挑剔的俱乐部,只允许独一无二的会员加入。如果有人想重复加入,对不起,直接拒之门外。
-
特点:
- 不允许重复元素。
- 元素没有顺序(虽然遍历时按照插入顺序)。
- 可以存储任何类型的数据。
-
基本用法:
-
创建Set:
let mySet = new Set(); // 创建一个空Set let initialSet = new Set([1, 2, 3, 4, 5]); // 用数组初始化Set
-
添加元素:
mySet.add(1); mySet.add(2); mySet.add(2); // 重复添加,Set会自动忽略 console.log(mySet); // 输出: Set(2) {1, 2}
-
删除元素:
mySet.delete(1); console.log(mySet); // 输出: Set(1) {2}
-
检查元素是否存在:
console.log(mySet.has(2)); // 输出: true console.log(mySet.has(1)); // 输出: false
-
获取Set的大小:
console.log(mySet.size); // 输出: 1
-
清空Set:
mySet.clear(); console.log(mySet.size); // 输出: 0
-
-
Set的遍历:
Set提供了多种遍历方式:
for...of
循环、forEach()
方法和values()
方法。let mySet = new Set([1, 2, 3]); // 使用 for...of 循环 for (let item of mySet) { console.log(item); } // 输出: // 1 // 2 // 3 // 使用 forEach() 方法 mySet.forEach(function(value) { console.log(value); }); // 输出: // 1 // 2 // 3 // 使用 values() 方法 for (let value of mySet.values()) { console.log(value); } // 输出: // 1 // 2 // 3
-
Set的优势和应用场景:
-
数组去重: 这是Set最常见的应用场景。将数组转换为Set,再转换回数组,就能轻松去除重复元素。
let arr = [1, 2, 2, 3, 4, 4, 5]; let uniqueArr = [...new Set(arr)]; // 使用扩展运算符 console.log(uniqueArr); // 输出: [1, 2, 3, 4, 5]
相比于传统的循环判断方法,Set去重更加简洁高效。
-
判断元素是否存在:
has()
方法的查找速度非常快,接近O(1)复杂度。这在需要频繁判断元素是否存在的场景下非常有用。 -
数学集合运算: Set可以方便地进行并集、交集、差集等运算。
-
并集:
let setA = new Set([1, 2, 3]); let setB = new Set([3, 4, 5]); let union = new Set([...setA, ...setB]); console.log(union); // 输出: Set(5) {1, 2, 3, 4, 5}
-
交集:
let setA = new Set([1, 2, 3]); let setB = new Set([3, 4, 5]); let intersection = new Set([...setA].filter(x => setB.has(x))); console.log(intersection); // 输出: Set(1) {3}
-
差集:
let setA = new Set([1, 2, 3]); let setB = new Set([3, 4, 5]); let difference = new Set([...setA].filter(x => !setB.has(x))); // A - B console.log(difference); // 输出: Set(2) {1, 2}
-
-
记录访问过的元素: 比如,在爬虫程序中,可以使用Set来记录已经访问过的URL,防止重复抓取。
-
-
Set 与数组的比较:
特性 Set 数组 是否允许重复 否 是 元素顺序 插入顺序 (虽然没有索引) 有索引,严格按照插入顺序 查找速度 has()
方法接近 O(1)indexOf()
或循环查找,平均 O(n)应用场景 去重、判断元素是否存在、集合运算等 存储有序数据、频繁访问元素、数组操作等
二、Map:键值对的集合,更灵活的“对象”
Map 是一种存储键值对的数据结构,类似于对象,但它比对象更灵活。对象的键只能是字符串或 Symbol,而 Map 的键可以是任何类型的数据,包括对象、函数等。
-
特点:
- 键值对存储,键可以是任何类型。
- 键是唯一的,值可以重复。
- 保持键的插入顺序。
-
基本用法:
-
创建Map:
let myMap = new Map(); // 创建一个空Map let initialMap = new Map([ ['name', 'Alice'], ['age', 30], [true, 'isStudent'] ]); // 用数组初始化Map
-
添加键值对:
myMap.set('name', 'Bob'); myMap.set(1, 'number'); myMap.set({}, 'object'); // 键可以是对象 console.log(myMap); // 输出: // Map(3) { // 'name' => 'Bob', // 1 => 'number', // {} => 'object' // }
-
获取值:
console.log(myMap.get('name')); // 输出: Bob console.log(myMap.get(1)); // 输出: number
-
删除键值对:
myMap.delete('name'); console.log(myMap); // 输出: // Map(2) { // 1 => 'number', // {} => 'object' // }
-
检查键是否存在:
console.log(myMap.has(1)); // 输出: true console.log(myMap.has('name')); // 输出: false
-
获取Map的大小:
console.log(myMap.size); // 输出: 2
-
清空Map:
myMap.clear(); console.log(myMap.size); // 输出: 0
-
-
Map的遍历:
Map 提供了多种遍历方式:
for...of
循环、forEach()
方法、keys()
方法、values()
方法和entries()
方法。let myMap = new Map([ ['name', 'Alice'], ['age', 30], ['city', 'New York'] ]); // 使用 for...of 循环 (entries()) for (let [key, value] of myMap) { console.log(key, value); } // 输出: // name Alice // age 30 // city New York // 使用 forEach() 方法 myMap.forEach(function(value, key) { console.log(key, value); }); // 输出: // name Alice // age 30 // city New York // 使用 keys() 方法 for (let key of myMap.keys()) { console.log(key); } // 输出: // name // age // city // 使用 values() 方法 for (let value of myMap.values()) { console.log(value); } // 输出: // Alice // 30 // New York // 使用 entries() 方法 for (let entry of myMap.entries()) { console.log(entry); } // 输出: // [ 'name', 'Alice' ] // [ 'age', 30 ] // [ 'city', 'New York' ]
-
Map的优势和应用场景:
-
键可以是任何类型: 这是Map最显著的优势。当需要使用对象、函数等作为键时,Map是唯一的选择。
let obj1 = { id: 1 }; let obj2 = { id: 2 }; let myMap = new Map(); myMap.set(obj1, 'Object 1'); myMap.set(obj2, 'Object 2'); console.log(myMap.get(obj1)); // 输出: Object 1
-
保持键的插入顺序: Map 会记住键的插入顺序,这在某些需要按照特定顺序处理数据的场景下非常有用。
-
更好的性能: 在频繁添加和删除键值对的场景下,Map 的性能通常比对象更好。
-
存储元数据: 可以使用 Map 来存储与 DOM 元素或其他对象相关的元数据,而不会污染这些对象本身。
let element = document.createElement('div'); let metadata = new Map(); metadata.set(element, { tooltip: 'This is a div element', className: 'highlighted' }); // 获取元素的元数据 let elementMetadata = metadata.get(element); console.log(elementMetadata.tooltip); // 输出: This is a div element
-
缓存: Map 可以用作缓存,存储计算结果,避免重复计算。键可以是函数的参数,值可以是函数的返回值。
-
-
Map 与对象的比较:
特性 Map 对象 键的类型 任何类型 字符串或 Symbol 键的顺序 保持插入顺序 不保证顺序 (ES6 之后大部分浏览器保持插入顺序,但不应该依赖) 性能 在频繁添加和删除键值对时通常更好 在访问已知属性时通常更快 迭代 内置迭代器,易于遍历 需要使用 Object.keys()
,Object.values()
等方法应用场景 需要使用非字符串键、需要保持键的顺序等 存储简单数据、配置对象等
三、Set 和 Map 的实际应用例子
为了更好地理解 Set 和 Map 的应用,我们来看几个实际的例子。
-
使用 Set 实现简单的投票系统:
let voters = new Set(); function vote(voterId) { if (voters.has(voterId)) { console.log(`Voter ${voterId} has already voted.`); } else { voters.add(voterId); console.log(`Voter ${voterId} voted successfully.`); } } vote(123); // 输出: Voter 123 voted successfully. vote(456); // 输出: Voter 456 voted successfully. vote(123); // 输出: Voter 123 has already voted. console.log(`Total votes: ${voters.size}`); // 输出: Total votes: 2
-
使用 Map 实现一个简单的缓存系统:
let cache = new Map(); function expensiveCalculation(input) { console.log(`Calculating for input: ${input}`); // 模拟耗时计算 let result = input * 2; return result; } function cachedCalculation(input) { if (cache.has(input)) { console.log(`Fetching from cache for input: ${input}`); return cache.get(input); } else { let result = expensiveCalculation(input); cache.set(input, result); return result; } } console.log(cachedCalculation(5)); // 输出: Calculating for input: 5 10 console.log(cachedCalculation(5)); // 输出: Fetching from cache for input: 5 10 console.log(cachedCalculation(10)); // 输出: Calculating for input: 10 20 console.log(cachedCalculation(10)); // 输出: Fetching from cache for input: 10 20
-
使用 Map 存储数据条目的访问次数
let data = [ { id: 1, name: 'Item A' }, { id: 2, name: 'Item B' }, { id: 3, name: 'Item C' } ]; let accessCounts = new Map(); // 初始化访问计数 data.forEach(item => { accessCounts.set(item.id, 0); }); function accessItem(itemId) { if (accessCounts.has(itemId)) { let count = accessCounts.get(itemId); accessCounts.set(itemId, count + 1); console.log(`Item ${itemId} accessed. Access count: ${count + 1}`); } else { console.log(`Item ${itemId} not found.`); } } accessItem(1); accessItem(2); accessItem(1); accessItem(3); console.log(accessCounts); // 输出 Map(3) { // 1 => 2, // 2 => 1, // 3 => 1 // }
四、总结
Set 和 Map 是 JavaScript 中非常有用的数据结构,它们分别提供了存储唯一元素和键值对的强大功能。相比于数组和对象,它们在某些场景下具有更高的效率和灵活性。
- Set 适合: 需要存储唯一值,进行集合运算,或者需要快速判断元素是否存在的情况。
- Map 适合: 需要使用非字符串键,需要保持键的插入顺序,或者需要在键值对之间建立复杂关系的情况。
希望今天的讲解能帮助大家更好地理解和使用 Set 和 Map。记住,它们不是万能的,但绝对是你的工具箱里不可或缺的利器。
好啦,今天的讲座就到这里,感谢大家的收听!下次再见,拜拜! 记住,编程的路上,多学习,多实践,多思考,才能成为真正的 “代码段子手”!