五、ES6+详情篇—Set和Map


1、set集合

一直以来,JS只能使用数组和对象来保存多个数据,缺乏像其他语言那样拥有丰富的集合类型。因此,ES6新增了两种集合类型(set 和 map),用于在不同的场景中发挥作用。

set用于存放不重复的数据

  1. 如何创建set集合
new Set(); //创建一个没有任何内容的set集合

new Set(iterable); //创建一个具有初始内容的set集合,内容来自于可迭代对象每一次迭代的结果
  1. 如何对set集合进行后续操作
  • add(数据): 添加一个数据到set集合末尾,如果数据已存在,则不进行任何操作
    • set使用Object.is的方式判断两个数据是否相同,但是,针对+0和-0,set认为是相等
  • has(数据): 判断set中是否存在对应的数据
  • delete(数据):删除匹配的数据,返回是否删除成功
  • clear():清空整个set集合
  • size: 获取set集合中的元素数量,只读属性,无法重新赋值
  1. 如何与数组进行相互转换
const s = new Set([x,x,x,x,x]);
// set本身也是一个可迭代对象,每次迭代的结果就是每一项的值
const arr = [...s];
  1. 如何遍历

1). 使用for-of循环

2). 使用set中的实例方法forEach

注意:set集合中不存在下标,因此forEach中的回调的第二个参数和第一个参数是一致的,均表示set中的每一项

<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
  </head>

  <body>
    <script>
      const s1 = new Set();

      s1.add(1);
      s1.add(2);
      s1.add(3);
      s1.add(1); //无效
      s1.add(+0);
      s1.add(-0); //无效

      // for (const item of s1) {
      //     console.log(item)
      // }

      s1.forEach((item, index, s) => {
        console.log(item, index, s);
      })
      console.log(s1);
      console.log("总数为:", s1.size);
    </script>

    <script>
      const arr = [45, 7, 2, 2, 34, 46, 6, 57, 8, 55, 6, 46];
      const result = [...new Set(arr)];
      console.log(result);

      const str = "asf23sdfgsdgfsafasdfasfasfasfsafsagfdsfg";
      const s = [...new Set(str)].join("");
      console.log(s);
    </script>
  </body>

</html>

2、set应用

直接上代码

<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
  </head>

  <body>
    <script>
      // 两个数组的并集、交集、差集 (不能出现重复项),得到的结果是一个新数组
      const arr1 = [33, 22, 55, 33, 11, 33, 5];
      const arr2 = [22, 55, 77, 88, 88, 99, 99];

      //并集
      // const result = [...new Set(arr1.concat(arr2))];
      console.log("并集", [...new Set([...arr1, ...arr2])]);

      const cross = [...new Set(arr1)].filter(item => arr2.indexOf(item) >= 0);
      //交集
      console.log("交集", cross)

      //差集
      // console.log("差集", [...new Set([...arr1, ...arr2])].filter(item => arr1.indexOf(item) >= 0 && arr2.indexOf(item) < 0 || arr2.indexOf(item) >= 0 && arr1.indexOf(item) < 0))
      console.log("差集", [...new Set([...arr1, ...arr2])].filter(item => cross.indexOf(item) < 0))
    </script>
  </body>

</html>

3、map集合

键值对(key value pair)数据集合的特点:键不可重复

map集合专门用于存储多个键值对数据。

在map出现之前,我们使用的是对象的方式来存储键值对,键是属性名,值是属性值。

使用对象存储有以下问题:

  1. 键名只能是字符串
  2. 获取数据的数量不方便
  3. 键名容易跟原型上的名称冲突
  4. 如何创建map
new Map(); //创建一个空的map
new Map(iterable); //创建一个具有初始内容的map,初始内容来自于可迭代对象每一次迭代的结果,但是,它要求每一次迭代的结果必须是一个长度为2的数组,数组第一项表示键,数组的第二项表示值
  1. 如何进行后续操作
  • size:只读属性,获取当前map中键的数量
  • set(键, 值):设置一个键值对,键和值可以是任何类型
    • 如果键不存在,则添加一项
    • 如果键已存在,则修改它的值
    • 比较键的方式和set相同
  • get(键): 根据一个键得到对应的值
  • has(键):判断某个键是否存在
  • delete(键):删除指定的键
  • clear(): 清空map
  1. 和数组互相转换

和set一样

  1. 遍历
  • for-of,每次迭代得到的是一个长度为2的数组
  • forEach,通过回调函数遍历
    • 参数1:每一项的值
    • 参数2:每一项的键
    • 参数3:map本身
<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
  </head>

  <body>
    <script>
      const mp = new Map([
        ["a", 3],
        ["c", 10],
        ["b", 4],
        ["c", 5]
      ]);
      const result = [...mp]
      console.log(result);

      // for (const [key, value] of mp) {
      //     console.log(key, value)
      // }

      mp.forEach((value, key, mp) => {
        console.log(value, key, mp)
      })
    </script>
  </body>

</html>

4、WeakSet和WeakMap

WeakSet

使用该集合,可以实现和set一样的功能,不同的是:

  1. 它内部存储的对象地址不会影响垃圾回收
  2. 只能添加对象
  3. 不能遍历(不是可迭代的对象)、没有size属性、没有forEach方法

WeakMap

类似于map的集合,不同的是:

  1. 它的键存储的地址不会影响垃圾回收
  2. 它的键只能是对象(说明如果键不是对象的话可能会影响垃圾回收)
  3. 不能遍历(不是可迭代的对象)、没有size属性、没有forEach方法
<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
  </head>

  <body>
    <script>
      let obj = {
        name: "yj",
        age: 18
      };
      const set = new Set();
      const set = new WeakSet();
      set.add(obj);
      obj = null;
      // 如果你用Set你会发现,set里面的obj还在
      // 当obj被赋值为null的时候,obj所指向的对象并没有被浏览器垃圾回收
      // 因为set里有索引指向了它,obj为null,是obj的索引指向了null

      // 但是如果你用weakset就会发现,set里的值被回收了
      console.log(set)
    </script>
    <script>
      let obj = {
        name: "yj",
        age: 18
      };
      let obj2 = obj;
      const set = new WeakSet();
      set.add(obj);

      obj = null;
      obj2 = null;
      console.log(set)
    </script>
  </body>

</html>

注意:上面会遇到一个问题,set和weakset都打印出obj指向的对象了,本质上来说weakset不应该打印,而是因该回收的。原因如下,问的GPT

你提到WeakSet不影响垃圾回收的理解是正确的,但要注意的是,垃圾收集机制的触发条件和时机是由 JavaScript 引擎来决定的,不是立即触发的。你设置了obj = null;后,虽然你手动将obj置为null,但这只是一个标志,JavaScript 引擎会在合适的时机执行垃圾回收。

当你执行console.log(set)时,垃圾回收可能尚未执行,因此在输出中你仍然可以看到之前添加到WeakSet的对象。具体何时进行垃圾回收,取决于 JavaScript 引擎的实现和当前的内存使用情况。

此外,打印WeakSet的结果可能不能直接反映其中的内容,因为WeakSet不能像普通的Set一样直接迭代和打印。再次强调,WeakSet的主要特性是它不会阻止其引用对象被垃圾收集。

一个示例代码如下展示:

let obj = {
  name: "yj",
  age: 18
};
const set = new WeakSet();
set.add(obj);

obj = null;
// 尝试强制垃圾回收(此功能仅适用于某些特定环境,浏览器中通常不可用)
if (globalThis.gc) globalThis.gc(); // 在Node.js中可以使用--expose-gc来启用

setTimeout(() => {
  console.log(set);
}, 1000); // 通过延时使垃圾收集器有时间运行,实际情况中未必有效

需要注意的是,通过通常的代码试图控制或预期垃圾回收的行为是很困难且不可靠的,垃圾回收的触发机制完全由JavaScript引擎控制。

<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
  </head>

  <body>
    <ul>
      <!-- { id:"1", name:"姓名1" } -->
      <li>1</li>
      <!-- { id:"2", name:"姓名2" } -->
      <li>2</li>
      <!-- { id:"3", name:"姓名3" } -->
      <li>3</li>
    </ul>
    <script>
      const wmap = new WeakMap();
      let lis = document.querySelectorAll("li");
      for (const li of lis) {
        wmap.set(li, {
          id: li.innerHTML,
          name: 姓名${li.innerHTML}
        });
      }
      lis[0].remove();
      lis = null;

      console.log(wmap);
    </script>
  </body>

</html>

文章作者: 吴俊杰
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 吴俊杰 !
 上一篇
六、ES6+详情篇—迭代器和生成器 六、ES6+详情篇—迭代器和生成器
我一直没有明白这两个东西到底有什么用,听也听不懂,后来慢慢通过理解数组和对象为什么可以迭代和循环知道了它们的作用......
2024-12-03
下一篇 
四、ES6+详情篇—Symbol 四、ES6+详情篇—Symbol
我对于Symbol的理解就是它是给大牛写框架用的,当然我们也能用,但是比较少,作为对象的私有属性,ES6+越来越像后端的模式了......
2024-12-03
  目录