likes
comments
collection
share

Set和Map:带你轻松理解弱引用

作者站长头像
站长
· 阅读数 26

一:Set

1.1:创建Set对象

使用new Set()构造函数创建一个新的空Set对象

  • "Set":表明这是一个Set对象。
  • (0):括号内的数字表示Set中当前元素的数量,这里的0意味着Set是空的,没有元素。
  • {}:花括号表示Set的边界,由于Set为空,所以在花括号之间没有任何内容。
const s = new Set()
console.log(s)//输出Set(0) {}

在创建Set对象时,可以接收参数

  1. new Set()构造函数接受一个可迭代对象([1, 2, 3, 3])作为参数。
  2. Set构造函数会遍历这个可迭代对象,并将其中的每个元素添加到新的Set
const s = new Set([1, 2, 3, 3])  // 具有iterable属性的结构
console.log(s);//输出Set(3) { 1, 2, 3 }

注意

  • Set的性质要求元素必须是唯一的,所以即使数组中有重复的元素(如这里的3),Set也只会存储一个该元素的实例,
  • 接收的参数类型必须是具有迭代性属性的结构,即可以被原始方法遍历的

1.2:Set中的基本操作

  • add(value) :向Set中添加一个值,如果值已存在,则不做任何操作
  • delete(value) :从Set中移除一个值,如果值不存在,则不做任何操作
  • has(value) :检查Set中是否包含某个值,返回一个布尔值
  • clear() :清除Set中的所有元素
  • size:返回Set中元素的数量
  • for...of:遍历具有迭代性的属性结构
  • key:Set中元素的唯一标识
let mySet = new Set();

// 添加元素
mySet.add('Hello');
mySet.add({name: 'John'}); // 可以添加对象

// 检查元素是否存在
console.log(mySet.has('Hello')); // true

// 移除元素
mySet.delete('Hello');

// 清空Set
mySet.clear();

// 遍历Set
for (let item of mySet) {// 专门用来遍历具有iterable属性的结构
    console.log(item);
}

//key   
mySet.forEach((val, key) => {//forEach也可以遍历Set
  console.log(val, key);
})

二:Set特性的作用

2.1:数组去重

  • new Set(arr):创建一个新的Set对象,传入arr数组作为参数。Set会自动去除重复的元素,只保留唯一的值。
  • [...new Set(arr)]:使用扩展运算符(...)将Set对象转换回数组。扩展运算符会将集合中的所有元素提取出来,形成一个新的数组。
const arr = [1, 2, 3, 4, 4, 3, 2, 1]
const arr2 = [...new Set(arr)]
console.log(arr2);

2.2:字符串去重

  • new Set(str):同数组去重中作用一样,但这里的关键在于,字符串被视为一系列可迭代的字符,因此可以被Set构造函数正确处理。
  • [...new Set(str)]:使用扩展运算符(...)将Set对象转换成一个数组。
  • 使用join('')方法将数组转换回字符串。
const str = 'abcabc'
console.log([...new Set(str)].join(''));

三:WeakSet

3.1:弱引用与垃圾回收

阮一峰老师书中的原话:

WeakSet 只能放置对象和 Symbol值。其次,WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。

第一句话好理解,难点在于弱引用以及垃圾回收机制。主要可以概括为以下简单的两点:

  1. 一个对象obj存在了其他的结构中,当后续存在其他对象引用这个对象,那么这个对象的内存就会不会被回收。
  2. 一个对象obj存在了其他的结构中,当后续只存在WeakSet对它的引用,该对象的内存依然会被回收

下面用一个代码模拟来加深理解

  1. 引入全局垃圾回收:在Node.js环境中,global.gc()是一个函数调用,它触发垃圾回收器立即运行。

通常,垃圾回收是由JavaScript引擎自动管理的,其机制何时运行是不可预测的,所以开发者不能精确控制。但是global.gc()提供了一个接口,允许开发者手动触发垃圾回收。

  1. 创建大对象并使用弱引用:创建一个较大的对象obj,包含名字属性和一个非常大的数组age,再将对象添加到WeakSet集合ws中,
  2. 断开强引用:将obj设置为null,原先指向obj的强引用被断开。
  3. 强制垃圾回收:再次调用global.gc()来强制执行垃圾回收。
global.gc()  // 清理干净,防止对后续代码产生误差
let obj = {name: 'midsummer', age: new Array(5 * 1024 * 1024)}
let ws = new WeakSet()
ws.add(obj)

obj = null  // 重置为空,告诉v8引擎来回收
global.gc()//这里如果没有obj = null 这个代码,垃圾回收不会执行,因为垃圾回收不受人类控制,不能确定回收时间,只能确定一定会被回收

console.log(process.memoryUsage()) //读取当前代码占据的内存

友友们可自行注释第六行代码,对比注释前后的输出结果,相信就能理解弱引用了。所以WeakSet 的成员是不适合引用的,因为它会随时消失。 同样的,Map也有弱引用WeakMap,这里就不做重复介绍了,具体可以看看阮一峰老师的书,这两者相似,理解了WeakSet,WeakMap也就不在话下了

3.2:WeakSet作用

下面通过一个小demo来帮助友友们能够更加直观的体会到其作用

  1. HTML结构:页面包含一个<div>容器和一个<button>按钮,它们分别由wrapbtn变量在JavaScript中引用。

  2. DOM元素引用与WeakSet:创建一个WeakSet对象disabledEls,并将btn按钮添加到其中。如果btn按钮没有其他强引用指向它,即使disabledEls中包含对它的引用,按钮元素仍然可以被垃圾回收器回收。

  3. 按钮点击事件:当按钮被点击时,removeChild方法被调用来移除按钮元素。移除后,btn元素将不再存在于DOM树中,且如果没有其他强引用指向它,btn元素可以被垃圾回收。

<body>
  <div id="wrap">
    <button id="btn">确认</button>
  </div>
  <script>
    let wrap = document.getElementById("wrap");
    let btn = document.getElementById("btn");  // null
    
    // 为了给按钮打上禁用标签
    const disabledEls = new WeakSet()  // new Set()
    disabledEls.add(btn)
    btn.addEventListener("click", () => {
      wrap.removeChild(btn)
    })//如果用的是强引用,按钮被移除之后,`btn`元素不会被垃圾回收。
  </script>
</body>

通过以上案例的分析,我们可知,使用WeakSet的主要优势在于,即使按钮被从DOM树中移除,disabledEls中的引用也不会阻止按钮元素被垃圾回收。这有助于避免内存泄漏,确保不再需要的DOM元素能够及时释放内存,提高应用的性能和响应速度。

四:Map

Map值得一提的点是可以用任意数据做key,弥补了传统对象的不足

4.1:Map 中的基本操作

  • new Map() :构造函数创建一个新的空 Map 对象
  • set(key, value) :向 Map 中添加或更新一个键值对。
  • get(key) :获取 Map 中对应键的值。
  • delete(key) :从 Map 中移除一个键值对。
  • has(key) :检查 Map 中是否包含某个键,返回一个布尔值。
  • clear() :清除 Map 中的所有键值对。
  • size:返回 Map 中键值对的数量。
  • entries()keys()values() :返回迭代器,分别用于迭代键值对、键和值。
let myMap = new Map();

// 添加键值对
myMap.set('Hello', 'World');
myMap.set({name: 'John'}, 'Developer'); // 可以使用对象作为键

// 获取值
console.log(myMap.get('Hello')); // 输出 "World"

// 检查键是否存在
console.log(myMap.has('Hello')); // 输出 true

// 移除键值对
myMap.delete('Hello');

// 清空 Map
myMap.clear();

// 遍历 Map
for (let [key, value] of myMap.entries()) {
  console.log(key, value);
}

// 使用 keys(), values(), entries()
for (let key of myMap.keys()) {
  console.log(key);
}

for (let value of myMap.values()) {
  console.log(value);
}

for (let [key, value] of myMap.entries()) {
  console.log(key, value);
}

4.2: Map 与 Set 的相似点

  • 都基于键值对:尽管 Set 实际上是键值对的一种特殊情况,其中键和值相同,但两者都使用键值对的概念。
  • 都支持迭代Set 和 Map 都可以使用 for...of 循环或其他迭代方法来遍历其内容。
  • 都有基本操作Set 和 Map 都支持类似的操作,如添加、删除和检查元素的存在。

五:总结

  • Set:用于存储唯一元素,支持去重操作,如数组和字符串去重。同时支持 adddeletehasclearsize 等基本操作,可通过 for...of 循环或 forEach 方法遍历元素,

  • WeakSet:通过弱引用管理对象,有助于避免内存泄漏。当对象不再有强引用时,即使存在于 WeakSet 中,也可以被垃圾回收。这在处理DOM元素时特别有用,确保不再需要的元素能够及时释放内存,WeakMap与其相似

  • Map:允许使用任意数据类型作为键,增强了键值对的灵活性。Map 支持 setgetdeletehasclearsize 等基本操作,可以通过 for...of 循环或 forEach 方法遍历键值对。

转载自:https://juejin.cn/post/7395145769731407909
评论
请登录