引言:
ES6(ECMAScript 2015)引入了两个新的重要数据结构:Set(集合)和Map(映射),它们扩展了JavaScript原有的数据处理能力,提供了更灵活和高效的数据存储方式。
正文
1. Set
主要特点
-
唯一性:Set集合中的每个值都是唯一的,没有重复的值。
-
任意类型:Set集合中的值可以是任何类型的,包括基本类型和引用类型。
-
迭代顺序:Set集合会按插入的顺序迭代其元素,这意味着每次迭代的顺序是有序的。
简单概括一句话:成员唯一,无重复项。
Set的使用方法
- 创建Set:
ini
let mySet = new Set([iterable]);
可以通过一个可迭代对象(如数组)来初始化Set。
- add(value) :
csharp
mySet.add(1);
向Set中添加一个值。
-
delete(value) :
gomySet.delete(1); // true
从Set中删除一个值,返回一个布尔值,表示删除是否成功。
-
has(value) :
rubymySet.has(1); // false
检查Set中是否存在某个值,返回布尔值。
-
clear() :
inimySet.clear();
清空Set中所有的值。
-
size:
arduinomySet.size; // 0
返回Set对象中的值的个数。
scss
// 创建一个新的Set
let mySet = new Set([1, 2, 3, 4, 4]);
// 添加值
mySet.add(5);
mySet.add(1); // 1 已经存在于Set中,因此不会添加
console.log(mySet.has(3)); // true
console.log(mySet.size); // 5
// 删除值
mySet.delete(2);
console.log(mySet.size); // 4
// 清空Set
mySet.clear();
console.log(mySet.size); // 0
遍历操作:
Set 结构的实例有四个遍历方法,可以用于遍历成员。
-
Set.prototype.keys()
:返回键名的遍历器 -
Set.prototype.values()
:返回键值的遍历器 -
Set.prototype.entries()
:返回键值对的遍历器 -
Set.prototype.forEach()
:使用回调函数遍历每个成员
1)keys()
,values()
,entries()
keys
方法、values
方法、entries
方法返回的都是遍历器对象(详见《Iterator 对象》一章)。由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys
方法和values
方法的行为完全一致。
csharp
let set = new Set(['red', 'green', 'blue']);
for (let item of set.keys()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.values()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.entries()) {
console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]
Set的应用场景
数组去重:
ini
let numbers = [1, 2, 2, 3, 4, 4, 5];
let uniqueNumbers = [...new Set(numbers)];
console.log(uniqueNumbers); // [1, 2, 3, 4, 5]
集合操作:
-
并集:
javascriptlet setA = new Set([1, 2, 3]); let setB = new Set([3, 4, 5]); let union = new Set([...setA, ...setB]); console.log(union); // Set { 1, 2, 3, 4, 5 }
-
交集:
javascriptlet 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 { 3 }
-
差集:
javascriptlet setA = new Set([1, 2, 3]); let setB = new Set([3, 4, 5]); let difference = new Set([...setA].filter(x => !setB.has(x))); console.log(difference); // Set { 1, 2 }
Set是一种非常有用的数据结构,尤其是在需要确保数据唯一性或进行集合操作时。它提供了一种高效且简洁的方式来处理这些常见的编程需求。
WeakSet
首先,WeakSet 的成员只能是对象和 Symbol 值,而不能是其他类型的值。
它与Set
相似,但有几个关键区别。WeakSet
主要用于存储对象的弱引用,这意味着它们不会阻止垃圾回收机制回收这些对象。
主要特性
-
只能存储对象 :
WeakSet
只能包含对象,而不能包含其他类型的值(如字符串、数字等)。 -
弱引用 :存储在
WeakSet
中的对象是弱引用的,这意味着如果没有其他引用,该对象可以被垃圾回收机制回收。 -
不可遍历 :由于其弱引用的特性,
WeakSet
是不可遍历的,不能使用for...of
循环或其他迭代方法来获取其内容。
看看一个例子的引用:
xml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="wrap">
<button id="btn">确定</button>
</div>
<script>
let wrap = document.getElementById('wrap')
let btn = document.getElementById('btn')
const disabledEls = new Set()
disabledEls.add(btn)
btn.addEventListener("click",() => {
wrap.removeChild(btn)
})
</script>
</body>
</html>
在这里wrap的子容器btn按钮的移除,V8系统中的垃圾回收机制并不会清除btn 的内存,但是改成: const disabledEls = new WeakSet()垃圾回收机制就会清理掉它所占据的内存。
官方文档中这样说:
这里或许会有点难懂,转换成通俗一点来讲拆分成两句话来理解:
-
一个对象obj存放在了其他的结构中,当后续存在其他对象引用这个对象,那么这个对象的内存就不会被回收。
-
一个对象obj存在了其他的结构中,当后续只存在WeakSet对它的引用,该对象的内存依然会被回收
Map
JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。
为了解决这个问题,ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是"键"的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了"字符串---值"的对应,Map 结构提供了"值---值"的对应,是一种更完善的 Hash 结构实现。如果你需要"键值对"的数据结构,Map 比 Object 更合适。
基本方法
如何向Map添加成员的:
scss
const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content')
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false
但是作为构造函数,Map 也可以接受一个数组作为参数:
arduino
const map = new Map([
['name', '李四'],
['title', 'Author']
]);
map.size // 2
map.has('name') // true
map.get('name') // "李四"
map.has('title') // true
map.get('title') // "Author"
Map 结构的实例有以下属性和操作方法。
(1)size 属性
size
属性返回 Map 结构的成员总数。
arduino
const map = new Map();
map.set('foo', true);
map.set('bar', false);
map.size // 2
(2)Map.prototype.set(key, value)
set
方法设置键名key
对应的键值为value
,然后返回整个 Map 结构。如果key
已经有值,则键值会被更新,否则就新生成该键。
dart
const m = new Map();
m.set('edition', 6) // 键是字符串
m.set(262, 'standard') // 键是数值
m.set(undefined, 'nah') // 键是 undefined
set
方法返回的是当前的Map
对象,因此可以采用链式写法。
csharp
let map = new Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c');
(3)Map.prototype.get(key)
get
方法读取key
对应的键值,如果找不到key
,返回undefined
。
javascript
const m = new Map();
const hello = function() {console.log('hello');};
m.set(hello, 'Hello ES6!') // 键是函数
m.get(hello) // Hello ES6!
(4)Map.prototype.has(key)
has
方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。
dart
const m = new Map();
m.set('edition', 6);
m.set(262, 'standard');
m.set(undefined, 'nah');
m.has('edition') // true
m.has('years') // false
m.has(262) // true
m.has(undefined) // true
(5)Map.prototype.delete(key)
delete()
方法删除某个键,返回true
。如果删除失败,返回false
。
javascript
const m = new Map();
m.set(undefined, 'nah');
m.has(undefined) // true
m.delete(undefined)
m.has(undefined) // false
(6)Map.prototype.clear()
clear()
方法清除所有成员,没有返回值。
arduino
let map = new Map();
map.set('foo', true);
map.set('bar', false);
map.size // 2
map.clear()
map.size // 0
至于Map的遍历也跟Set具有类似的效果。
WeakMap
WeakMap
结构与Map
结构类似,也是用于生成键值对的集合。
WeakMap
只接受对象(null
除外)和 Symbol 值作为键名,不接受其他类型的值作为键名。
核心:面试官的提问:解释出Set(Map)和WeakSet(WeakMap)的区别:
最基本的思想就是:
- 它是弱引用
-
一个对象obj存放在了其他的结构中,当后续存在其他对象引用这个对象,那么这个对象的内存就不会被回收。
-
一个对象obj存在了其他的结构中,当后续只存在WeakSet(WeakMap)对它的引用,该对象的内存依然会被回收
这样的回答概念理解好在进行诉说就行。