一、引入背景

  Set集合是一种无重复元素的列表,开发者们一般不会逐一读取数组中的元素,也不太可能逐一访问Set集合中的每个元素,通常的做法是检测给定的值在某个集合中是否存在

  Map集合内含多组键值对,集合中每个元素分别存放着可访问的键名和它对应的值,Map集合经常被用于缓存频繁取用的数据。在标准正式发布以前,开发者们已经在ES5中用非数组对象实现了类似的功能

  ES6新标准将Set集合与Map集合添加到JS中。

  在ES5中,开发者们用对象属性来模拟这两种集合

let set = Object.create(null);
set.foo = true;
// 检查属性的存在性
if (set.foo) {
// 一些操作
}

  这里的变量set是一个原型为null的对象,不继承任何属性。在ES5中,开发者们经常用类似的方法检查对象的某个属性值是否存在。 在这个示例中,将set.foo赋值为true,通过if语句可以确认该值存在于当前对象中

  模拟这两种集合对象的唯一区别是存储的值不同,以下这个示例是用对象模拟Map集合

let map = Object.create(null);
map.foo = "bar";
// 提取一个值
let value = map.foo;
console.log(value); // "bar"

  这段代码将字符串"bar"储存在map.foo中。一般来说,Set集合常被用于检查对象中是否存在某个键名,而Map集合常被用于获取已存的信息

  如果程序很简单,确实可以用对象来模拟Set集合与Map集合,但如果触碰到对象属性的某些限制,那么这个方法就会变得更加复杂。例如,所有对象的属性名必须是字符串类型,必须确保每个键名都是字符串类型且在对象中是唯一的

let map = Object.create(null);
map[] = "foo";
console.log(map[""]); // "foo"

  本例中将对象的某个属性赋值为字符串"foo",而这个属性的键名是数值型的5,它会被自动转换成字符串,所以map["5"]和map[5]引用的其实是同一个属性。如果想分别用数字和字符串作为对象属性的键名,则内部的自动转换机制会导致很多问题。当然,用对象作为属性的键名也会遇到类似的问题

let map = Object.create(null),
key1 = {},
key2 = {};
map[key1] = "foo";
console.log(map[key2]); // "foo"

  由于对象属性的键名必须是字符串,因而这段代码中的key1和key2将被转换为对象对应的默认字符串"[object Object]",所以map[key2]和map[key1]引用的是同一个属性。这种错误很难被发现,用不同对象作为对象属性的键名理论上应该指向多个属性,但实际上这种假设却不成立

  由于对象会被转换为默认的字符串表达方式,因此其很难用作对象属性的键名

  对于Map集合来说,如果它的属性值是假值,则在要求使用布尔值的情况下(例如在if语句中)会被自动转换成false。强制转换本身没有问题,但如果考虑这个值的使用场景,就有可能导致错误发生

let map = Object.create(null);
map.count = ;
// 是想检查 "count" 属性的存在性,还是想检查非零值?
if (map.count) {
// ...
}

  这个示例中有一些模棱两可的地方,比如我们应该怎样使用map.count?if语句中,我们是检查map.count是否存在,还是检查值是否非零。在示例中,由于value的值是1,为真值,if语句中的代码将被执行。然而,如果map.count的值为0或者不存在,if语句中的代码块将不会被执行

  在大型软件应用中,一旦发生此类问题将难以定位及调试,从而促使ES6在语言中加入Set集合与Map集合这两种新特性

  当然,在JS中有一个in运算符,不需要读取对象的值就可以判断属性在对象中是否存在,如果存在就返回true。但是,in运算符也会检索对象的原型,只有当对象原型为null时使用这个方法才比较稳妥

二、Set集合

  ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。通过Set集合可以快速访问其中的数据,更有效地追踪各种离散值

  Set 结构的实例有以下属性:(类似java里面)

Set.prototype.constructor:构造函数,默认就是Set函数

Set.prototype.size:返回Set实例的成员总数

  Set 实例的操作方法(用于操作数据)包括以下4个:

add(value):添加某个值,返回Set结构本身

has(value):返回一个布尔值,表示该值是否为Set的成员

delete(value):删除某个值,返回一个布尔值,表示删除是否成功

clear():清除所有成员,没有返回值

1、创建Set集合、add()添加元素

  调用new Set()创建Set集合,调用add()方法向集合中添加元素,访问集合的size属性可以获取集合中目前的元素数量

let set = new Set();
set.add();
set.add("");
console.log(set.size); //

  在Set集合中,不会对所存值进行强制的类型转换,数字5和字符串"5"可以作为两个独立元素存在

const s = new Set();
[, , , , , , ].forEach(x => s.add(x));
for (let i of s) {
console.log(i);
}
// 2 3 5 4

  上面代码通过add方法向 Set 结构加入成员,结果表明 Set 结构不会添加重复的值。

  当然,如果向Set集合中添加多个对象,则它们之间彼此保持独立

let set = new Set(),
  key1 = {},
  key2 = {};
set.add(key1);
set.add(key2);
console.log(set.size); //

  由于key1和key2不会被转换成字符串,因而它们在Set集合中是两个独立的元素;如果被转换, 则二者的值都是'[object Object]'

  如果多次调用add()方法并传入相同的值作为参数,那么后续的调用实际上会被忽略

let set = new Set();
set.add();
set.add("");
set.add(); // 重复了,该调用被忽略
console.log(set.size); //

  由于第二次传入的数字5是一个重复值,因此其不会被添加到集合中,所以控制台最后输出的Set集合size属性值为2

  可以使用数组来初始化一个 Set ,并且 Set 构造器会确保不重复地使用这些值

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

  在这个示例中,我们用一个含重复元素的数组来初始化Set集合,数组中有4个数字5,而在生成的集合中只有一个。自动去重的功能对于将已有代码或JSON结构转换为Set集合执行得非常好

  实际上,Set构造函数可以接受所有可迭代对象作为参数,数组、Set集合、Map集合都是可迭代的,因而都可以作为Set构造函数的参数使用;构造函数通过迭代器从参数中提取值

2、has()检测元素

  通过has()方法可以检测Set集合中是否存在某个值

let set = new Set();
set.add();
set.add("");
console.log(set.has()); // true
console.log(set.has()); // false
//在这段代码中,set集合里没有数字6这个值,所以set.has(6)调用返回false

3、delete()和clear()移除元素

  调用delete()方法可以移除Set集合中的某一个元素,调用clear()方法会移除集合中的所有元素

let set = new Set();
set.add();
set.add("");
console.log(set.has()); // true
set.delete();
console.log(set.has()); // false
console.log(set.size); //
set.clear();
console.log(set.has("")); // false
console.log(set.size); // 0
//调用delete(5)之后,只有数字5被移除;执行clear()方法后,Set集合中的所有元素都被清除了

4、遍历操作

  Set 结构的实例有四个遍历方法,可以用于遍历成员

keys():返回键名的遍历器

values():返回键值的遍历器

entries():返回键值对的遍历器

forEach():使用回调函数遍历每个成员

5、keys()、values()、entries()

  keys方法、values方法、entries方法返回的都是遍历器对象。由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为完全一致(map的话就不一样)

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"]

  上面代码中,entries方法返回的遍历器,同时包括键名和键值,所以每次输出一个数组,它的两个成员完全相等(对于set集合来说,entries返回的键名和键值完全相等

  Set 结构的实例默认可遍历,它的默认遍历器生成函数就是它的values方法

Set.prototype[Symbol.iterator] === Set.prototype.values// true
let set = new Set(['red', 'green', 'blue']);

for (let x of set) {
console.log(x);
}
// red
// green
// blue

  这意味着,可以省略values方法,直接用for...of循环遍历 Set

6、forEach()

  Set结构的实例的forEach方法,用于对每个成员执行某种操作,没有返回值

let set = new Set(['a','b','c']);
set.forEach((key, value, set) => { console.log(key,value,set);} )
//a a ['a','b','c']
//b b ['a','b','c']
//c c ['a','b','c']

  上面代码说明,forEach方法的参数就是一个处理函数。该函数的参数依次为键值、键名、集合本身

  在Set集合的forEach()方法中,第二个参数也与数组的一样,如果需要在回调函数中使用this引用,则可以将它作为第二个参数传入forEach()函数

let set = new Set([, ]);
let processor = {
output(value) {
console.log(value);
},
process(dataSet) {
dataSet.forEach(function(value) {
this.output(value);
}, this);
}
};
processor.process(set);

  以上示例中,processor.process()方法调用了Set集合的forEach()方法并将this传入作为回调函数的this值,从而this.output()方法可以正确调用processor.output()方法。forEach()方法的回调函数只使用了第一个参数value,所以直接省略了其他参数。在这里也可以使用箭头函数,这样就无须再将this作为第二个参数传入回调函数了

let set = new Set([, ]);
let processor = {
output(value) {
console.log(value);
},
process(dataSet) {
dataSet.forEach((value) => this.output(value));
}
};
processor.process(set);

  在此示例中,箭头函数从外围的process()函数读取this值,所以可以正确地将this.output()方法解析为一次processor.output()调用

  注意:尽管Set集合更适合用来跟踪多个值,而且又可以通过forEach()方法操作集合中的每一个元素,但是不能像访问数组元素那样直接通过索引访问集合中的元素。如有需要,最好先将Set集合转换成一个数组

7、将Set集合转换为数组

  将数组转换为Set集合的过程很简单,只需给Set构造函数传入数组即可;将Set集合再转回数组的过程同样很简单,需要用到展开运算符(...),它可以将数组中的元素分解为各自独立的函数参数。展开运算符也可以将诸如Set集合的可迭代对象转换为数组

let set = new Set([, , , , , , ]),
array = [...set];
console.log(array); // [1,2,3,4,5]

  在这里,用一个含重复元素的数组初始化Set集合,集合会自动移除这些重复元素然后再用展开运算符将这些元素放到一个新的数组中。Set集合依然保留创建时接受的元素(1、2、3、4、5),新数组中保存着这些元素的副本

  如果已经创建过一个数组,想要复制它并创建一个无重复元素的新数组,则上述这个方法就非常有用

function eliminateDuplicates(items) {
return [...new Set(items)];//先用set构造方法创建set,再用展开运算符将set转为无重复元素的数组
}
let numbers = [, , , , , , ],
noDuplicates = eliminateDuplicates(numbers);
console.log(noDuplicates); // [1,2,3,4,5]

  在以上函数中,Set集合仅是用来过滤重复值的临时中介,最后会输出新创建的无重复元素的数组

三、WeakSet

  将对象存储在Set的实例与存储在变量中完全一样,只要Set实例中的引用存在,垃圾回收机制就不能释放该对象的内存空间,于是之前提到的Set类型可以被看作是一个强引用的Set集合

let set = new Set(),
key = {};
set.add(key);
console.log(set.size); // 1
// 取消原始引用
key = null;
console.log(set.size); // 1
// 重新获得原始引用
key = [...set][0];

  在这个示例中,将变量key设置为null时便清除了对初始对象的引用,但是Set集合却保留了这个引用,仍然可以使用展开运算符将Set集合转换成数组格式并从数组的首个元素取出该引用

  大部分情况下这段代码运行良好,但有时候会希望当其他所有引用都不再存在时,让Set集合中的这些引用随之消失。举个例子,如果在Web页面中通过JS代码记录了一些DOM元素,这些元素有可能被另一段脚本移除,而又不希望自己的代码保留这些DOM元素的最后一个引用

  为了解决这个问题,ES6中引入了另外一个类型:WeakSet集合(弱引用Set集合)

1、创建WeakSet集合

  用Weakset构造函数可以创建WeakSet集合,集合支持3个方法:add()、has()和delete()

let set = new WeakSet(),
key = {};
// 将对象加入 set
set.add(key);
console.log(set.has(key)); // true
set.delete(key);
console.log(set.has(key)); // false

  WeakSet集合的使用方式与Set集合类似,可以向集合中添加引用,从中移除引用,也可以检査集合中是否存在指定对象的引用。也可以调用WeakSet构造函数并传入一个可迭代对象来创建WeakSet集合

let key1 = {},
key2 = {},
set = new WeakSet([key1, key2]);
console.log(set.has(key1)); // true
console.log(set.has(key2)); // true

  以上示例中,向WeakSet构造函数传入一个含有两个对象的数组,最终创建包含这两个对象的WeakSet集合

2、与Set集合的区别

  WeakSet与Set最大的区别是WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于WeakSet之中

let set = new WeakSet(),
key = {};
set.add(key);
console.log(set.has(key)); // true
// 取消原始引用
key = null;
console.log(set.has(key)); // false

  由于上面这个特点,WeakSet的成员是不适合引用的,因为它会随时消失。另外,由于WeakSet内部有多少个成员,取决于垃圾回收机制有没有运行,运行前后很可能成员个数是不一样的,而垃圾回收机制何时运行是不可预测的,因此ES6规定WeakSet不可遍历

  除了以上主要区别之外,它们之间还有下面几个差别

  (1)在Weakset的实例中,如果向add()、has()和delete()这3个方法传入非对象参数都会导致程序报错

  (2)WeakSet集合不可迭代,所以不能被用于for-of循环

  (3)WeakSet集合不暴露任何迭代器(例如keys()和values()方法),所以无法通过程序本身来检测其中的内容

  (4)WeakSet集合不支持forEach()方法

  (5)WeakSet集合不支持size属性

  WeakSet集合的功能看似受限,其实这是为了让它能够正确地处理内存中的数据。总之,如果只需要跟踪对象引用,更应该使用Weak Set集合而不是普通的Set集合

ES6中Set集合(与java里类似)的更多相关文章

  1. ES6中的Map集合(与java里类似)

    Set类型可以用来处理列表中的值,但是不适用于处理键值对这样的信息结构.ES6也添加了Map集合来解决类似的问题 一.Map集合 JS的对象(Object),本质上是键值对的集合(Hash结构),但是 ...

  2. ES6中的类

    前面的话 大多数面向对象的编程语言都支持类和类继承的特性,而JS却不支持这些特性,只能通过其他方法定义并关联多个相似的对象,这种状态一直延续到了ES5.由于类似的库层出不穷,最终还是在ECMAScri ...

  3. python学习之【第七篇】:Python中的集合及其所具有的方法

    1.前言 python中的集合set与列表类似,它们最大的区别是集合内不允许出现重复元素,如果在定义时包含重复元素,会自动去重. 集合是无序的,集合中的元素必须是不可变类型.集合可以作为字典的key. ...

  4. Java中的集合与线程的Demo

    一.简单线程同步问题 package com.ietree.multithread.sync; import java.util.Vector; public class Tickets { publ ...

  5. ES6中的Set和Map集合

    前面的话 在ES6标准制定以前,由于可选的集合类型有限,数组使用的又是数值型索引,因而经常被用于创建队列和栈.如果需要使用非数值型索引,就会用非数组对象创建所需的数据结构,而这就是Set集合与Map集 ...

  6. Java中的集合框架-Map

    前两篇<Java中的集合框架-Commection(一)>和<Java中的集合框架-Commection(二)>把集合框架中的Collection开发常用知识点作了一下记录,从 ...

  7. Java中的集合和常用类

    Java中的常用类: ▪ Object类 ▪ Math类 ▪ String类和StringBuffer类(字符串) ▪ 8种基本类型所对应的包装类 ▪ java.util包中的类——Date类 Obj ...

  8. 牛客网Java刷题知识点之Java 集合框架的构成、集合框架中的迭代器Iterator、集合框架中的集合接口Collection(List和Set)、集合框架中的Map集合

    不多说,直接上干货! 集合框架中包含了大量集合接口.这些接口的实现类和操作它们的算法. 集合容器因为内部的数据结构不同,有多种具体容器. 不断的向上抽取,就形成了集合框架. Map是一次添加一对元素. ...

  9. Java中的集合Map、HashMap、Hashtable、Properties、SortedMap、TreeMap、WeakHashMap、IdentityHashMap、EnumMap(五)

    Map Map用于保存具有映射关系的数据,因此Map集合里保存着两组值,一组值用于保存Map里的key,另一组值用于保存Map里的value,key和value都可以是任何引用类型的数据.Map的ke ...

随机推荐

  1. 圆盘自动机 cell

    圆盘自动机 cell 一个n-m圆盘自动机,包含n个排列成一圈的方格,它们按1至n编号.每个方格中有一个整数,范围[0,m-1] .圆盘会进行d操作,每次d操作会使得每个方格中的数同时变换,变换为与其 ...

  2. 【BZOJ 4151 The Cave】

    Time Limit: 5 Sec  Memory Limit: 256 MBSec  Special JudgeSubmit: 293  Solved: 144[Submit][Status][Di ...

  3. 【ZBH选讲·树变环】

    [问题描述] 你是能看到第三题的friends呢.——aoao 树是个好东西,删掉树一条边要1的代价,随便再加一条边有1的代价,求最小的代价把树变成环.[输入格式] 第一行一个整数,代表树的点数.接下 ...

  4. Guns V2.5

    Guns V2.5 新版Guns基于SpringBoot全面升级,完美整合springmvc + shiro + MyBatis 通用 Mapper + 分页插件 PageHelper + beetl ...

  5. 牛客网暑期ACM多校训练营(第十场)D Rikka with Prefix Sum (数学)

    Rikka with Prefix Sum 题意: 给出一个数组a,一开始全为0,现在有三种操作: 1.  1 L R W,让区间[L,R]里面的数全都加上W: 2.  2     将a数组变为其前缀 ...

  6. poj1679 次最小生成树 kruskal(暴力枚举)

    Description Given a connected undirected graph, tell if its minimum spanning tree is unique. Definit ...

  7. COMPANY_点取消会卡死的解决方法

    // OLD void ctonedlg::onbtn_basedir_clicked() {     m_basedir = getUserSelectDir();     doSearchDir( ...

  8. Pom报错

    maven的pom报plugins错误的解决方法 maven的pom报plugins错误的解决方法. 引用 Failure to transfer org.apache.maven.plugins:m ...

  9. ajax和json数据

    一.Ajax概述 1.什么是同步,什么是异步 同步现象:客户端发送请求到服务器端,当服务器返回响应之前,客户端都处于等待     卡死状态 异步现象:客户端发送请求到服务器端,无论服务器是否返回响应, ...

  10. Jquery操作基本筛选过滤器

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...