Set集合


ES6学习系列 😃

长久以来,数组一直是JavaScript中唯一的集合类型,不过,有一些开发者认为非数组对象也是集合,只不过是键值对集合,它们的用途与数组完全相同。在ES6之前,由于可选的集合类型有限,数组使用的又是数值型索引,因而经常被用于创建队列和栈。如果开发者们需要使用非数值型索引,就会用非数组对象创建所需的数据解构,而这是Set集合与Map集合的早期实现。
Set集合是一种无重复元素的列表,开发者们一般不会像访问数组元素那样逐一访问每个元素,通常的做法是检测给定的值在某个集合中是否存在。Map集合内含多组键值对,集合中每个元素分别存放着可访问的键名和它对应的值,Map集合经常被用于缓存频繁取用的数据。在ES6标准正式发布以前,开发者们已经在ES5中用非数组对象实现了类似的功能。

ES5中的Set集合和Map集合

用对象模拟集合

var set = Object.create(null);
set.foo = true;

// 检查属性是否存在
if(set.foo){
    // 要执行的代码
}

变量set是一个原型为null的对象,不继承任何属性,在ES5中,开发者们经常用类似的方法检测对象的某个属性是否存在,在这个示例中,将set.foo赋值为true,通过条件语句可以确认该值存在于当前对象中。
模拟Map集合唯一的区别在于存储的值不同。

var map = Object.create(null);
map.foo = "bar";

// 获取值
var value = map.foo;
// bar
console.log(value);
该解决方案的一些问题

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

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

本来属性名是数值型的 5,但是会自动转换为字符串,最后map[“5”]和map[5]引用的是同一个属性。因此如果想分别用数字和字符串作为对象属性的键名,则内部的自动转换机制会导致很多问题。
同样的,用对象最为属性的键名也会遇到类似的问题。

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

对象也会自动转换为字符串作为键名。用不同对象作为对象属性的键名理论上应该指向多个属性,但实际上都是同一个属性"[object Object]"。由于对象会被转换为默认的字符串表达方式,因此其很难用作对象舒心的键名。
对于Map集合来说,如果他的属性值是假值,则在要求使用布尔值的情况下会被自动转为false。强制转换本身没有问题,但是某些场景下,就会导致错误发生。

var map = Object.create(null);
map.count = 0;
// 本来是检查count属性是否存在,实际上检查的是改值是否非零
if(map.count){
    // 要执行的代码 这里并不会执行
    console.log(map.count);
}

这个示例中,本来是检查map中是否包含count,但是count为0时,if语句中的代码却不会被执行。因为这里会被自动转换为false。
在大型软件中,一旦发生此类问题将难以定位及调试,从而ES6中需要加入Set集合和Map集合这两种新特性。

ES6中的Set集合
  • 创建Set集合并添加元素

Set集合不会对值进行强制的类型转换,数字5和字符串"5"可以作为两个独立元素存在,引擎内部通过Object.is方法检测两个值是否一致,唯一的例外是,Set集合中的+0和-0被认为时相等的。

// 调用new Set创建Set集合
let set = new Set();
// 通过add方法添加元素
set.add(5);
set.add("5");
/*
    * 通过size属性获取集合中目前的元素数量,Set集合不会对值进行强制的类型转换
    * 数字5和字符串"5"可以作为两个独立元素存在
    * 引擎内部通过Object.is方法检测两个值是否一致,唯一的例外是,Set集合中的+0和-0被认为时相等的
    */
// 2
console.log(set.size);

同样,向Set集合中添加两个对象,它们之间彼此保持独立。

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

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

let set = new Set();
set.add(5);
set.add("5");
// 重复 - 本次调用直接被忽略
set.add(5);
// 2
console.log(set.size);

由于第二次传入的数字5是一个重复值,因此不会被添加到集合中。
也可以用数组来初始化Set集合,Set构造函数同样会过滤掉重复的值从而保证集合中的元素各自唯一。

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

自动去重的功能对于将已有代码或者JSON结构转换为Set集合执行得非常好。

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

  • 检测元素是否存在

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

let set = new Set();
set.add(5);
set.add("5");
// true
console.log(set.has(5));
// false
console.log(set.has(6));
  • 移出元素

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

let set = new Set();
set.add(5);
set.add("5");
// true
console.log(set.has(5));
set.delete(5);
// false
console.log(set.has(5));
// 1
console.log(set.size);
set.clear();
// false
console.log(set.has("5"));
// 0
console.log(set.size);
  • forEach遍历元素

forEach方法中的回调参数接受以下三个参数

  1. Set集合中下一次索引的位置
  2. 与第一个参数一样的值
  3. 被遍历的Set集合本身
    其实这和数组和Map是一致的。数组和Map集合的forEach方法的回调参数都接受3个参数,前两个分别是值和键名(对于数组来说就是数值型索引值)。
let set = new Set([1,2]);
set.forEach(function(value,key,ownerSet){
    console.log(key + " " + value);
    console.log(ownerSet === set);
});

执行结果如下

1 1
true
2 2
true

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

let set = new Set([1,2]);
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([1,2]);
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集合转换为一个数组。

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

删除数组中的重复元素,可以将数组转为集合然后再转为数组即可、

function eliminateDuplicates(items) {
    return [...new Set(items)];
}
let nums = [1, 2, 3, 3, 3, 4, 5];
let noDuplicates = eliminateDuplicates(nums);
// [1, 2, 3, 4, 5]
console.log(noDuplicates);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lang20150928

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值