自学内容网 自学内容网

全面解析 Map、WeakMap、Set、WeakSet

目录

Map 是什么?

Map 实例属性和方法

WeakMap 是什么? 

WeakMap 常用方法

WeakMap 使用场景举例

Set 是什么?

创建 Set

Set 实例属性和方法

遍历 Set

Set 与数组的转换

Set 使用场景

WeakSet 是什么? 

WeakSet 特点

WeakSet实例方法

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 值,否则会新生成该键

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

let map = new Map()
map.set('name', 'Jasmine')
// 获取 name 的值
let name = map.get('name')
console.log(name)
// Jasmine
  • has()

判断传入的键是否存在当前 Map 对象中,该方法返回一个布尔值

let map = new Map()
map.set('name', 'Jasmine')
console.log(map.has('name')) // true
console.log(map.has('age')) // false
  • delete()

删除传入的键,返回 true,如果删除失败,则返回 false

let map = new Map()
map.set('name', 'Jasmine')
// 删除 name
map.delete('name')
// 再次查看是否还存在 name
console.log(map.has('name')) // false
  • clear()

清空 Map,删除所有键值对

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 中键值对的数量

let map = new Map()
map.set('name', 'Jasmine').set('sex', 'Female').set('age', 31)
console.log(map.size)
// 3
  • forEach(cb)

遍历 Map 中的每一个键值对,cb 函数依次接收三个参数:值、键、Map 本身

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 来遍历

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

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] 的形式返回

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

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 转为数组

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 的回收行为。

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 会自动释放对应的键值对,从而避免内存泄漏。

// 示例:使用 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 可以用于存储临时数据,这些数据只在特定的时间段内有用。由于它们的弱引用特性,当不再需要这些数据时,它们会被自动释放,不会造成内存泄漏。

// 示例:使用 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 也可以用于缓存数据,尤其是在一些数据可能会变得很大的情况下。由于它们的弱引用特性,可以确保在不再需要这些数据时,它们会被自动释放,避免占用过多的内存。

// 示例:使用 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 构造函数创建一个新的集合:

let mySet = new Set();

还可以传递一个可迭代对象(如数组)来初始化集合:

let mySet = new Set([1, 2, 3, 4]);

Set 实例属性和方法

  • add()

向集合中添加一个值。如果值已经存在,则不执行任何操作。

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。

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。

let mySet = new Set([1,2,3,4,5])
console.log(mySet.has(3));  // true
console.log(mySet.has(6));  // false
  • clear()

清空集合,删除所有元素。

let mySet = new Set([1,2,3,4,5])
mySet.clear();  // 清空集合
console.log(mySet) // Set(0) {size: 0}
  • size

返回集合中元素的个数。

let mySet = new Set([1,2,3,4])
console.log(mySet.size); // 4

遍历 Set

  • for...of 循环
    for (const value of mySet) {
        console.log(value);
    }
  • forEach 方法
    mySet.forEach((value) => {
        console.log(value);
    });
  • 结合解构赋值
    /***
    因为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
    const myArray = [1, 2, 3, 4];
    const mySet = new Set(myArray);
    
  • Set 转换为数组
    let mySet = new Set([1, 2, 3, 4]);
    // Array.from 转换
    let myArr1 = Array.from(mySet);
    // 解构赋值
    let myArr1 = [...mySet]

Set 使用场景

  • 数组去重
    let arr = [2, 3, 4, 5, 6, 2, 5]
    console.log([... new Set(arr)]) // (5) [2, 3, 4, 5, 6]
  • 得到交集并集差集
    let 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可以用于追踪一组对象是否存在或是否被处理过,而不会阻止对象被垃圾回收。

示例:防止重复处理

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为对象关联一些元数据信息,而不需要修改对象本身。

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 可以用来避免循环引用,因为它的成员是弱引用的,不会阻止垃圾回收。

// 示例:使用 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 在处理内存泄漏和避免循环引用方面具有以下优势:

  1. 内存泄漏:WeakMap 和 WeakSet 的键是弱引用,这意味着如果一个对象不再被其他地方引用,那么它所对应的键也将不再被 WeakMap 或 WeakSet 引用,从而可以被垃圾回收器回收,避免了内存泄漏的问题
  2. 避免循环引用:循环引用是指两个或多个对象相互引用,导致它们无法被垃圾回收器回收。WeakMap 和 WeakSet 的弱引用特性可以帮助避免循环引用的问题,因为它们不会阻止垃圾回收器回收其他对象
  3. 性能优势:由于 WeakMap 和 WeakSet 的键是弱引用,它们不会对对象的生存时间产生影响,因此在某些情况下可以提高性能,尤其是在处理大量对象时。

总之,WeakMap 和 WeakSet 在处理内存泄漏和避免循环引用方面具有优势,可以帮助开发人员更好地管理内存和避免潜在的问题。

WeakMap 和 WeakSet:解决内存泄漏&避免循环引用(下)-阿里云开发者社区


原文地址:https://blog.csdn.net/m0_73531461/article/details/143947488

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!