深入理解ES6中的Set与Map:高效操作无序集合与键值对存储

引言:

ES6(ECMAScript 2015)引入了两个新的重要数据结构:Set(集合)和Map(映射),它们扩展了JavaScript原有的数据处理能力,提供了更灵活和高效的数据存储方式。

正文

1. Set

主要特点

  1. 唯一性:Set集合中的每个值都是唯一的,没有重复的值。

  2. 任意类型:Set集合中的值可以是任何类型的,包括基本类型和引用类型。

  3. 迭代顺序:Set集合会按插入的顺序迭代其元素,这意味着每次迭代的顺序是有序的。

简单概括一句话:成员唯一,无重复项。

Set的使用方法

  1. 创建Set
ini 复制代码
let mySet = new Set([iterable]);

可以通过一个可迭代对象(如数组)来初始化Set。

  1. add(value)
csharp 复制代码
mySet.add(1);

向Set中添加一个值。

  1. delete(value)

    go 复制代码
    mySet.delete(1); // true

    从Set中删除一个值,返回一个布尔值,表示删除是否成功。

  2. has(value)

    ruby 复制代码
    mySet.has(1); // false

    检查Set中是否存在某个值,返回布尔值。

  3. clear()

    ini 复制代码
    mySet.clear();

    清空Set中所有的值。

  4. size

    arduino 复制代码
    mySet.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]

集合操作

  • 并集

    javascript 复制代码
    let 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 }
  • 交集

    javascript 复制代码
    let 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 }
  • 差集

    javascript 复制代码
    let 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主要用于存储对象的弱引用,这意味着它们不会阻止垃圾回收机制回收这些对象。

主要特性

  1. 只能存储对象WeakSet 只能包含对象,而不能包含其他类型的值(如字符串、数字等)。

  2. 弱引用 :存储在 WeakSet 中的对象是弱引用的,这意味着如果没有其他引用,该对象可以被垃圾回收机制回收。

  3. 不可遍历 :由于其弱引用的特性,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()垃圾回收机制就会清理掉它所占据的内存。

官方文档中这样说:

这里或许会有点难懂,转换成通俗一点来讲拆分成两句话来理解:

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

  2. 一个对象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)的区别:

最基本的思想就是:

  • 它是弱引用
  1. 一个对象obj存放在了其他的结构中,当后续存在其他对象引用这个对象,那么这个对象的内存就不会被回收。

  2. 一个对象obj存在了其他的结构中,当后续只存在WeakSet(WeakMap)对它的引用,该对象的内存依然会被回收

这样的回答概念理解好在进行诉说就行。

相关推荐
fruge1 分钟前
纯css制作声波扩散动画、js+css3波纹催眠动画特效、【css3动画】圆波扩散效果、雷达光波效果完整代码
javascript·css·css3
neter.asia10 分钟前
vue中如何关闭eslint检测?
前端·javascript·vue.js
~甲壳虫11 分钟前
说说webpack中常见的Plugin?解决了什么问题?
前端·webpack·node.js
光影少年30 分钟前
vue2与vue3的全局通信插件,如何实现自定义的插件
前端·javascript·vue.js
As977_31 分钟前
前端学习Day12 CSS盒子的定位(相对定位篇“附练习”)
前端·css·学习
susu108301891133 分钟前
vue3 css的样式如果background没有,如何覆盖有background的样式
前端·css
Ocean☾34 分钟前
前端基础-html-注册界面
前端·算法·html
Rattenking35 分钟前
React 源码学习01 ---- React.Children.map 的实现与应用
javascript·学习·react.js
Dragon Wu37 分钟前
前端 Canvas 绘画 总结
前端
CodeToGym41 分钟前
Webpack性能优化指南:从构建到部署的全方位策略
前端·webpack·性能优化