目录
[Map 是什么?](#Map 是什么?)
[Map 实例属性和方法](#Map 实例属性和方法)
[WeakMap 是什么?](#WeakMap 是什么?)
[WeakMap 常用方法](#WeakMap 常用方法)
[WeakMap 使用场景举例](#WeakMap 使用场景举例)
[Set 是什么?](#Set 是什么?)
[创建 Set](#创建 Set)
[Set 实例属性和方法](#Set 实例属性和方法)
[遍历 Set](#遍历 Set)
[Set 与数组的转换](#Set 与数组的转换)
[Set 使用场景](#Set 使用场景)
[WeakSet 是什么?](#WeakSet 是什么?)
[WeakSet 特点](#WeakSet 特点)
[WeakSet 使用场景举例](#WeakSet 使用场景举例)
[回顾 WeakMap 和 WeakSet 的重要概念和特点](#回顾 WeakMap 和 WeakSet 的重要概念和特点)
Map 是什么?
Map 本质上是一个键值对的集合。相比于普通的对象(Object),Map提供了一些额外的功能和优势,解决了一些对象的限制和问题,包括:
- 键的数据类型不受限制:普通对象的键只能是字符串或符号(Symbol),而 Map 的键可以是任意数据类型,包括基本类型和对象等
- 键值对的顺序保证: Map保持键值对的插入顺序,迭代时会按照插入的顺序进行
- 方便获取键值对数量: Map提供了 size 属性,可以方便地获取键值对的数量
- 更灵活的迭代方式: Map提供了更灵活的迭代方式,包括 keys()、values() 和 entries() 等方法,使得对键、值或键值对的遍历更加方便
- 易于判断是否包含某个键: 与对象相比,Map 提供了 has 方法,用于判断是否包含指定的键
- 更好的性能: 在某些场景下,使用 Map 可能具有更好的性能,尤其是在频繁增删键值对的情况下,因为 Map 在这方面优于普通对象
Map 实例属性和方法
- set()
设置键名 key 对应的键值为 value,然后会返回整个 Map 结构,如果设置的 key 已经存在,则会更新 value 值,否则会新生成该键
javascript
let map = new Map()
map.set('name', 'Jasmine')
// 也可采用链式写法设置多组数据
map.set('sex', 'Female').set('age', 31)
// 更新 name 值
map.set('name', 'Sue')
console.log(map)
// Map(3) {'name' => 'Sue', 'sex' => 'Female', 'age' => 31}
- get()
通过 get 方法读取 key 对应的键值,如果传入的键值不存在,则会返回 undefined
javascript
let map = new Map()
map.set('name', 'Jasmine')
// 获取 name 的值
let name = map.get('name')
console.log(name)
// Jasmine
- has()
判断传入的键是否存在当前 Map 对象中,该方法返回一个布尔值
javascript
let map = new Map()
map.set('name', 'Jasmine')
console.log(map.has('name')) // true
console.log(map.has('age')) // false
- delete()
删除传入的键,返回 true
,如果删除失败,则返回 false
javascript
let map = new Map()
map.set('name', 'Jasmine')
// 删除 name
map.delete('name')
// 再次查看是否还存在 name
console.log(map.has('name')) // false
- clear()
清空 Map
,删除所有键值对
javascript
let map = new Map()
map.set('name', 'Jasmine').set('sex', 'Female').set('age', 31)
// 清空所有键值对
map.clear()
console.log(map)
// Map(0) {size: 0}
- size
返回 Map
中键值对的数量
javascript
let map = new Map()
map.set('name', 'Jasmine').set('sex', 'Female').set('age', 31)
console.log(map.size)
// 3
- forEach(cb)
遍历 Map
中的每一个键值对,cb 函数依次接收三个参数:值、键、Map 本身
javascript
let map = new Map()
map.set('name', 'Jasmine').set('sex', 'Female').set('age', 31)
map.forEach((value, key) => {
console.log(key, value)
})
// name: Jasmine
// sex: Female
// age: 31
- keys()
返回 Map
中所有键的迭代器(Iterator
),可以用 for...of
来遍历
javascript
let map = new Map()
map.set('name', 'Jasmine').set('sex', 'Female').set('age', 31)
for (let key of map.keys()) {
console.log(key);
}
// name
// sex
// age
- values()
返回 Map
中所有值的迭代器(Iterator
)
javascript
let map = new Map()
map.set('name', 'Jasmine').set('sex', 'Female').set('age', 31)
for (let key of map.values()) {
console.log(key);
}
// Jasmine
// Female
// 31
- entries()
返回 Map
中所有键值对的迭代器,每个键值对会以 [key, value] 的形式返回
javascript
let map = new Map()
map.set('name', 'Jasmine').set('sex', 'Female').set('age', 31)
for (let entry of map.entries()) {
console.log(entry);
}
// (2) ['name', 'Jasmine']
// (2) ['sex', 'Female']
// (2) ['age', 31]
for...of
遍历Map
javascript
let map = new Map()
map.set('name', 'Jasmine').set('sex', 'Female').set('age', 31)
for (let [key, value] of map) {
console.log(key, value);
}
// name: Jasmine
// sex: Female
// age: 31
- map 转为数组
javascript
let map = new Map()
map.set('name', 'Jasmine').set('sex', 'Female').set('age', 31)
let arr = [...map]
console.log(arr)
/*
(3) [Array(2), Array(2), Array(2)]
0: (2) ['name', 'Jasmine']
1: (2) ['sex', 'Female']
2: (2) ['age', 31]
length: 3
*/
WeakMap 是什么?
Map 可以解决对象的 key 不能为对象的缺陷,但是又随之而来一个缺点:耗费内存,强引用。
ES6(ECMAScript 2015)考虑到这一点,推出了 WeakMap,用于存储键值对,其中键必须是对象,值可以是任意类型。与Map不同的是,WeakMap 的键是弱引用,也就是说,如果键对象没有被其他地方引用,则它们可以被垃圾回收。这使得WeakMap非常适合缓存数据,因为当对象不再需要时,它们可以自动从 WeakMap 中删除,从而释放内存。
强引用:创建引用之后,无法被 GC(垃圾回收机制) 进行回收,强到设置了 null 也分不开。
弱引用:对对象的弱引用是指当该对象应该被 GC 回收时,不会阻止 GC 的回收行为。
javascript
let obj1 = {name: 'Jasmine'}
let obj2 = {name: 'Sue'}
// 申明两个变量,分别是Map类型与WeakMap类型
let map = new Map()
let weakMap = new WeakMap()
map.set(obj1, 1)
weakMap.set(obj2, 2)
// 将obj1与obj2置为null,obj1与obj2会被垃圾回收机制回收
obj1 = null
obj2 = null
/**
虽然obj1设为了空,但由于obj1与map还存在引用关系,故无法分开
WeakMap中,如果键对象没有被其他地方引用,则它们可以被垃圾回收
**/
console.log(map) // Map(1) {{...} => 1}
console.log(weakMap) // WeakMap {}
注意:WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用。
WeakMap 常用方法
WeakMap 中,每个键对自己所引用对象的引用都是弱引用,在没有其他引用和该键引用同一对象时,该对象将会被垃圾回收(相应的 key 则变成无效的),故 WeakMap 的 key 是不可枚举的。
- set(key, value)
- get(key)
- has(key)
- delete(key)
- clear()
上述方法具体使用可参考 Map 的实例属性和方法
注意事项
- 键必须是对象:如果尝试使用非对象作为键,将抛出
TypeError
- 不可迭代:由于键的弱引用特性,
WeakMap
无法被遍历,因此没有entries()
、keys()
、values()
等方法
WeakMap 使用场景举例
- 解决内存泄漏问题
内存泄漏是指程序中不再使用的对象仍然保留在内存中,导致内存占用过高,甚至可能导致程序崩溃。WeakMap 可以用来解决这个问题,因为它的键是弱引用的,当键不再被其他对象引用时,WeakMap 会自动释放对应的键值对,从而避免内存泄漏。
javascript
// 示例:使用 WeakMap 解决内存泄漏问题
class LeakyClass {
constructor() {
this.data = new Map();
}
// 添加数据
setData(key, value) {
this.data.set(key, value);
}
// 获取数据
getData(key) {
return this.data.get(key);
}
// 移除数据
removeData(key) {
this.data.delete(key);
}
}
// 创建 LeakyClass 的实例
let leakyObject = new LeakyClass();
// 将对象添加到 WeakMap 中,并将其作为键
let weakMap = new WeakMap();
weakMap.set(leakyObject, 'some data');
// 断开对 leakyObject 的引用
leakyObject = null;
// 检查 WeakMap 中是否仍然存在对应键值对
console.log(weakMap.has(leakyObject)); // false
在上述示例中,我们创建了一个LeakyClass
类,它有一个data
属性,用于存储数据。我们将leakyObject
添加到weakMap
中,并将其作为键。然后,我们断开对leakyObject
的引用,此时leakyObject
成为垃圾回收的候选对象。最后,我们检查weakMap
中是否仍然存在对应键值对。由于leakyObject
已经不再被引用,它将被垃圾回收,因此weakMap.has(leakyObject)
返回false
。
- 临时数据存储
WeakMap 可以用于存储临时数据,这些数据只在特定的时间段内有用。由于它们的弱引用特性,当不再需要这些数据时,它们会被自动释放,不会造成内存泄漏。
javascript
// 示例:使用 WeakMap 存储临时数据
let weakMap = new WeakMap();
// 创建一个临时对象,并将其添加到 WeakMap 中
let tempObject = {key: 'value'};
weakMap.set(tempObject, 'some data');
// 使用临时对象
console.log(weakMap.get(tempObject));
// 断开对临时对象的引用
tempObject = null;
// 等待垃圾回收
setTimeout(() => {
// 检查 WeakMap 中是否仍然存在对应键值对
console.log(weakMap.has(tempObject));
}, 1000);
在上述示例中,我们使用WeakMap
存储了一个临时对象和相关的数据。然后,我们断开对临时对象的引用,并等待垃圾回收。最后,WeakMap.has(tempObject)
返回false
,因为临时对象已经被回收。
- 缓存
WeakMap 也可以用于缓存数据,尤其是在一些数据可能会变得很大的情况下。由于它们的弱引用特性,可以确保在不再需要这些数据时,它们会被自动释放,避免占用过多的内存。
javascript
// 示例:使用 WeakMap 进行缓存
let weakMap = new WeakMap();
// 创建一个大型对象,并将其添加到 WeakMap 中
let largeObject = {key: 'value'};
weakMap.set(largeObject, 'cached data');
// 使用缓存的数据
console.log(weakMap.get(largeObject));
// 断开对大型对象的引用
largeObject = null;
// 等待垃圾回收
setTimeout(() => {
// 检查 WeakMap 中是否仍然存在对应键值对
console.log(weakMap.has(largeObject));
}, 1000);
在上述示例中,我们使用WeakMap
缓存了一个大型对象。然后,我们断开对大型对象的引用,并等待垃圾回收。最后,WeakMap.has(largeObject)
返回false
,因为大型对象已经被回收。
这些只是 WeakMap 和 WeakSet 的一些常见应用场景。实际使用中,具体的场景可能会有所不同,需要根据具体情况选择合适的数据结构来解决问题。
Set 是什么?
Set 是一种集合数据结构,用于存储唯一值的集合,这意味着在一个Set
中,任何值都只能出现一次,重复的值会被自动忽略。Set 类似于数组,但与数组不同的是,它确保了所有元素的唯一性,并且没有索引。Set 也是元素的有序集合,这意味着元素的检索顺序将与插入顺序相同。Set 适合用于当你需要从数组或其他可迭代对象中过滤掉重复项,或者当你需要检查一个值是否存在于集合中时。
创建 Set
可以使用 Set
构造函数创建一个新的集合:
javascript
let mySet = new Set();
还可以传递一个可迭代对象(如数组)来初始化集合:
javascript
let mySet = new Set([1, 2, 3, 4]);
Set 实例属性和方法
- add()
向集合中添加一个值。如果值已经存在,则不执行任何操作。
javascript
let mySet = new Set([1, 2, 3, 4]);
// 添加值5
mySet.add(5);
/**
1已经存在,不会重复添加,重复添加不会报错
返回WeakSet对象本身,可以链式调用
**/
mySet.add(1).add(6);
console.log(mySet) // Set(6) {1, 2, 3, 4, 5, 6}
- delete()
从集合中删除一个值。如果该值存在,则返回 true,否则返回 false。
javascript
let mySet = new Set([1,2,3,4,5])
mySet.delete(2); // 删除值2
console.log(mySet) // Set(4) {1, 3, 4, 5}
- has(value)
检查集合中是否存在某个值。返回 true 或 false。
javascript
let mySet = new Set([1,2,3,4,5])
console.log(mySet.has(3)); // true
console.log(mySet.has(6)); // false
- clear()
清空集合,删除所有元素。
javascript
let mySet = new Set([1,2,3,4,5])
mySet.clear(); // 清空集合
console.log(mySet) // Set(0) {size: 0}
- size
返回集合中元素的个数。
javascript
let mySet = new Set([1,2,3,4])
console.log(mySet.size); // 4
遍历 Set
-
for...of 循环
javascriptfor (const value of mySet) { console.log(value); }
-
forEach 方法
javascriptmySet.forEach((value) => { console.log(value); });
-
结合解构赋值
javascript/*** 因为Set方法返回的数据结构是类数组, 所以我们要使用Array.form()去将其转化为数组, 也可以用ES6的结构将其转化为数组 因为 Set 是值的集合,它没有键,只有值,所以遍历键和值的结果是一样 ***/ let mySet = new Set([1, 2, 3, 4]) // keys(): 返回键名的遍历器 console.log(Array.from(mySet.keys())) // (4) [1, 2, 3, 4] // values(): 返回键值的遍历器 console.log(Array.from(mySet.values())) // (4) [1, 2, 3, 4] // entries(): 返回键值对的遍历器(解构类数组) console.log(Array.from(mySet.entries())) /*** (4) [Array(2), Array(2), Array(2), Array(2)] 0: (2) [1, 1] 1: (2) [2, 2] 2: (2) [3, 3] 3: (2) [4, 4] ***/
Set
与数组的转换
-
数组转换为
Set
javascriptconst myArray = [1, 2, 3, 4]; const mySet = new Set(myArray);
-
Set
转换为数组javascriptlet mySet = new Set([1, 2, 3, 4]); // Array.from 转换 let myArr1 = Array.from(mySet); // 解构赋值 let myArr1 = [...mySet]
Set 使用场景
-
数组去重
javascriptlet arr = [2, 3, 4, 5, 6, 2, 5] console.log([... new Set(arr)]) // (5) [2, 3, 4, 5, 6]
-
得到交集并集差集
javascriptlet aSet = new Set([1, 2, 3]) let bSet = new Set([5, 6, 3, 4, 2]) // 并集 let union = [...new Set([...aSet, ...bSet])] console.log(union) // (6) [1, 2, 3, 5, 6, 4] // 交集 let intersect = [...aSet].filter(x => bSet.has(x)) console.log(intersect) // [2, 3] // 差集 /** 定义:设A,B是两个集合,由所有属于A且不属于B的元素构成的集合,叫做集合A与集合B的差集 (同理,由所有属于B且不属于A的元素构成的集合,叫做集合B与集合A的差集) **/ // aSet与bSet的差集 let differA = [...aSet].filter(x => !bSet.has(x)) console.log(differA) // [1] // bSet与aSet的差集 let differB = [...bSet].filter(x => !aSet.has(x)) console.log(differB) // (3) [5, 6, 4]
WeakSet 是什么?
WeakSet
是ES6(ECMAScript 2015)引入的一种新的集合类型,用于存储对象的集合,并且这些对象都是弱引用。弱引用的含义是,如果没有其他引用指向集合中的对象,垃圾回收器可以自动回收这些对象,而不会因为它们存在于WeakSet
中而阻止回收
WeakSet 特点
- 只能存储对象:WeakSet 只能包含对象引用,不能包含原始值(如字符串、数字、布尔值等),尝试添加非对象类型的值将抛出TypeError
- 对象的弱引用:集合中的对象不计入垃圾回收器的引用计数中,如果没有其他引用,垃圾回收器可以回收这些对象。
- 不可迭代:
WeakSet
无法被遍历,没有entries()
、keys()
、values()
、forEach()
等方法。 - 没有size属性:由于弱引用的特性,WeakSet 无法提供集合的大小信息。
WeakSet实例方法
WeakSet
对象提供了以下实例方法:
add(value)
delete(value)
has(value)
上述方法具体使用可参考 Set 的实例属性和方法
WeakSet 使用场景举例
- 追踪对象状态
WeakSet
可以用于追踪一组对象是否存在或是否被处理过,而不会阻止对象被垃圾回收。
示例:防止重复处理
javascript
const processedObjects = new WeakSet();
function process(obj) {
if (processedObjects.has(obj)) {
console.log('已经处理过该对象');
return;
}
// 执行处理逻辑
console.log('处理对象:', obj);
// 将对象添加到WeakSet中,标记为已处理
processedObjects.add(obj);
}
const obj1 = { name: 'Alice' };
const obj2 = { name: 'Bob' };
process(obj1); // 处理对象: { name: 'Alice' }
process(obj1); // 已经处理过该对象
process(obj2); // 处理对象: { name: 'Bob' }
- 关联元数据
可以使用WeakSet
为对象关联一些元数据信息,而不需要修改对象本身。
javascript
const visited = new WeakSet();
function traverse(node) {
if (visited.has(node)) {
return;
}
visited.add(node);
// 处理节点逻辑
console.log('访问节点:', node);
// 假设节点有子节点
if (node.children) {
node.children.forEach(traverse);
}
}
const node1 = { id: 1 };
const node2 = { id: 2 };
const node3 = { id: 3 };
node1.children = [node2, node3];
node2.children = [node3]; // node3被多个节点引用
traverse(node1);
// 输出:
// 访问节点: { id: 1, children: [ { id: 2, children: [ { id: 3 } ] }, { id: 3 } ] }
// 访问节点: { id: 2, children: [ { id: 3 } ] }
// 访问节点: { id: 3 }
- 避免循环引用
循环引用是指两个或多个对象之间相互引用,形成一个循环,导致这些对象无法被垃圾回收。WeakSet 可以用来避免循环引用,因为它的成员是弱引用的,不会阻止垃圾回收。
javascript
// 示例:使用 WeakSet 避免循环引用
class Node {
constructor(value) {
this.value = value;
this.children = new WeakSet();
}
addChild(node) {
this.children.add(node);
}
removeChild(node) {
this.children.delete(node);
}
}
// 创建两个 Node 对象,并形成循环引用
let node1 = new Node(1);
let node2 = new Node(2);
node1.addChild(node2);
node2.addChild(node1);
// 将 node1 添加到 WeakSet 中
let weakSet = new WeakSet();
weakSet.add(node1);
// 断开对 node1 的引用
node1 = null;
// 等待垃圾回收
setTimeout(() => {
// 检查 WeakSet 中是否仍然存在 node1
console.log(weakSet.has(node1));
}, 1000);
在上述示例中,我们创建了两个Node
对象,并通过addChild
方法形成循环引用。然后,我们将node1
添加到weakSet
中。最后,我们断开对node1
的引用,并等待垃圾回收。在垃圾回收之后,weakSet.has(node1)
返回false
,因为node1
已经被回收。
总结
回顾 WeakMap 和 WeakSet 的重要概念和特点
WeakMap 和 WeakSet 是ES6提供的新的数据结构,它们的特点和概念如下:
- WeakMap:类似于Map,但是键只能是对象类型,且键名所指向的对象是弱引用,
这意味着如果这个对象在其他地方没有被引用,那么它将会被垃圾回收
,这也是 WeakMap 的主要应用场景。 - WeakSet:类似于Set,但成员只能是对象类型,且成员对象是弱引用,
这意味着如果这个对象在其他地方没有被引用,那么它将会被垃圾回收
,这也是 WeakSet 的主要应用场景。
需要注意的是,WeakMap 和 WeakSet 的键是弱引用,这意味着垃圾回收机制可以自动回收不再被引用的键所对应的对象,而不用手动删除键或者值。
强调它们在处理内存泄漏和避免循环引用方面的优势
WeakMap 和 WeakSet 在处理内存泄漏和避免循环引用方面具有以下优势:
- 内存泄漏:WeakMap 和 WeakSet 的键是弱引用,这意味着如果一个对象不再被其他地方引用,那么它所对应的键也将不再被 WeakMap 或 WeakSet 引用,从而可以被垃圾回收器回收,避免了内存泄漏的问题。
- 避免循环引用:循环引用是指两个或多个对象相互引用,导致它们无法被垃圾回收器回收。WeakMap 和 WeakSet 的弱引用特性可以帮助避免循环引用的问题,因为它们不会阻止垃圾回收器回收其他对象。
- 性能优势:由于 WeakMap 和 WeakSet 的键是弱引用,它们不会对对象的生存时间产生影响,因此在某些情况下可以提高性能,尤其是在处理大量对象时。
总之,WeakMap 和 WeakSet 在处理内存泄漏和避免循环引用方面具有优势,可以帮助开发人员更好地管理内存和避免潜在的问题。