JavaScript 集合

javascript

JavaScript 集合

集合 Set 是 ES6 中新增的内置对象,它是一些值的集合,其中每个值都是唯一不重复的,这和数学意义上的集合相同。使用起来类似于数组,但 Set 与数组之间的最大区别是:

  • 集合不基于索引,不能根据集合中的条目在集合中的位置引用这些条目
  • 条目不能单独被访问,但可创建迭代器循环访问 Set
  • 元素是唯一的

使用构造函数 Set(iterable) 根据可迭代对象创建集合,通常是传递一个数组,将会从数组里面复制元素到 set 中,其中的元素数据类型可以不同,但重复的元素会自动移除

js
const games = new Set(['Super Mario Bros.', 'Banjo-Kazooie', 'Mario Kart', 'Super Mario Bros.']);
// 在创建 Set 时,会自动移除重复的条目
console.log(games); // Set {'Super Mario Bros.', 'Banjo-Kazooie', 'Mario Kart'}

修改集合

  • 方法 .add(value) 向集合添加条目,返回对象本身。如果尝试向集合添加重复的条目,系统不会报错,但是该条目不会添加到集合中,避免了数据的冗余。
  • 方法 .delete(value) 从集合中删除条目,返回一个布尔值,如果该元素存在,返回 true,否则返回 false
  • 方法 .clear() 清空集合,删除集合中的所有元素

集合长度

属性 .size 返回集合中的条目数

Warning

不能像数组那样通过索引访问 Set,因此要使用 .size 属性,而不是 .length 属性获取集合的大小

检查值

使用方法 .has(value) 检查集合中是否有 value 值。如果集合中有该值,则返回 true;如果不存在就返回 false

Tip

类似于数组方法 arr.find(value) 在每次插入值时检查是否重复,但是这样性能会很差,因为这个方法会遍历整个数组来检查每个元素。Set 内部对唯一性检查进行了更好的优化。

迭代

集合 set 是一个 JavaScript 内置的可迭代对象(默认迭代器与 set.values() 生成的迭代器相同),结合 for-of 循环结构遍历所有元素。

Tip

集合 set 与映射 map 类似,也有三种方法生成迭代器,这是为了与 Map 兼容。

  • set.keys() 返回一个的迭代器(与 set.values() 作用相同,这是为了兼容 Map),可遍历并返回所有的值
  • set.values() 返回一个值的迭代器,遍历并返回所有的值
  • set.entries() 返回一个的迭代器,遍历并返回所有的实体,它的存在也是为了兼容 map
Tip

集合与数组类似,提供了内置方法 .forEach( function(value, valueAgain, set) ) 对集合的每个元素执行一次回调函数处理。

js
let set = new Set(["oranges", "apples", "bananas"]);
for (let value of set) alert(value);   // orange, apples, bananas
// 与 forEach 相同:
set.forEach((value, valueAgain, set) => {
  alert(value);
});

但注意一件有趣的设置forEach(callback) 的回调函数有三个参数,一个 value,然后是 同一个值 valueAgain,最后是目标对象,这是为了与 Map 兼容,

Tip

迭代器除了可以用于 for-of 循环结构中,还可以使用方法 next() 手动遍历每一项

js
const months = new Set(['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']);
// 生成迭代器,使用方法 next() 手动循环访问元素
const iterator = months.values();
iterator.next(); // Object {value: 'January', done: false}
Warning

在 Set 中迭代总是按照值插入的顺序进行的,所以我们不能说这些集合是无序的,但是我们不能对元素进行重新排序,也不能直接按其编号来获取元素。

弱集合

根据 垃圾回收 规则可知 JavaScript 引擎在值可访问(并可能被使用)时将其存储在内存中,并不会将空间进行回收,⚠️ 通常当对象、数组这类数据结构在内存中时,它们的子元素,如对象的属性、数组的元素都是可以访问的(即使相应的变量/主存储器已经被覆盖),这会造成回收清理很麻烦。

js
let john = { name: "John" };   // 该对象能被访问,john 是它的引用
// 覆盖引用
john = null;   // 该对象将会被从内存中清除
// 当对象引用在数组结构中
let Ben = { name: "Ben" };
let array = [ Ben ];
// 覆盖引用
Ben = null;
// john 被存储在数组里, 所以它不会被垃圾回收机制回收
array[0];   // {name: "Ben"} 仍然可以通过 array[0] 来获取它

如果使用对象作为 Set 元素,那么当 Set 存在时,该对象也将存在(即使引用该对象的变量已经被重置,该内存空间也不会被回收),这会造成无用资源占用内存的问题,可以使用另一种称为 WeakSet 弱集合的数据结构代替,以解决垃圾回收的问题。

WeakSetset 很像,最根本的不同是它不会阻止垃圾回收机制回收对象以释放内存空间

js
let Thomson = { name: "Thomson" };
let weakSet = new WeakSet();
weakSet.add(Thomson);
Thomson = null;   // 覆盖引用,对象 { name: "Thomson" } 被回收
weakSet.has(Thomson);   // false

此外它还有以下关键区别:

  • 只能向 WeakSet 添加对象(而不能是原始值),如果你尝试添加对象以外的内容系统将报错
  • 对象只有在其它某个(些)地方能被访问的时候(主存储器未被覆盖),才能留在弱集合中。
  • WeakSet 支持方法 .add().has().delete() 但无法迭代(即不支持方法 .keys().values() 等生成迭代器)
  • 不支持 size 属性,没有 .clear() 方法
Tip

弱集合 WeakSet 不支持引用所有键或其计数的方法和属性,仅允许单个操作。这种限制是由其工作原理决定的,如果一个对象丢失了其它所有引用(像上面示例中对象 { name: "Ben" } 唯一引用是变量 Ben,被重置为 null),那么它就会被垃圾回收机制自动回收。但是在从技术的角度并不能准确知道 何时会被回收,这由 JavaScript 引擎决定的。因此,从技术上讲 WeakMap 的当前元素的数量是未知的。JavaScript 引擎可能清理了其中的垃圾,可能没清理,也可能清理了一部分。因此暂不支持访问 WeakMap 的所有键/值的方法。

弱集合 WeakSet 一般充当额外的存储空间,存储与对象相关的「是/否」信息的事实,即当对象相关的信息为真时,该对象会存储在弱集合种;而当主存储器被覆盖(对象引用被「移除」)对象也会从弱集合中回收,表示对象相关信息为否。

js
// 使用弱集合记录追踪访问过网站的用户
let visitedSet = new WeakSet();
let john = { name: "John" };
let pete = { name: "Pete" };
let mary = { name: "Mary" };
visitedSet.add(john); // John 访问了我们
visitedSet.add(pete); // 然后是 Pete
visitedSet.add(john); // John 再次访问
// visitedSet 现在有两个用户了
// 检查 John 是否来访过?
alert(visitedSet.has(john)); // true
// 检查 Mary 是否来访过?
alert(visitedSet.has(mary)); // false
john = null;   // 同时 { name: "John" } 对象引用会从弱集合中回收移除
Tip

WeakSet 一般被用作「主要」对象存储之外的「辅助」数据结构。一旦将对象从主存储器(变量引用被重置)中删除,如果该对象仅被用作 WeakSet 元素,那么它将被自动清除。


Copyright © 2024 Ben

Theme BlogiNote

Icons from Icônes