ES6上面的set集合和map集合
- set集合
- 基本用法
- size函数
- add函数
- delete函数
- keys(),values(),entries()
- forEach()
- 遍历的应用
- Map集合
- 含义和基本用法
- 遍历方法
- 与其他数据结构的互相转换
- 1、Map 转为数组
- 2、数组 转为 Map
- 3、Map 转为对象
- 4、对象转为 Map
- 5、Map 转为 JSON
- 6、JSON 转为 Map
其实set集合和map集合在ES5的时候就有的,只是那个时候很少的人去使用这两个的,然后到现在ES6里面才使用这两个集合多起来的。
set集合
基本用法
set本身是一个构造函数,用来生成set数据机构
const arr = [2, 3, 5, 4, 5, 2, 2];
const s = new Set(arr);
console.log(s)//这个输出的是去重后的新数组[2,3,5,4]
size函数
size函数是表示数组的长度。
// 例一
const set = new Set([1, 2, 3, 4, 4]);
[...set] // [1, 2, 3, 4]
// 例二
const item = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
item.size // 5 ,获取到去重后的数组的元素个数
// 例三
const set = new Set(document.querySelectorAll('div'));//这个获取到的是页面上的所有div元素
set.size // 56
用set方法去重可以实现字符串和数组去重,如下:
const arr = [2, 3, 5, 4, 5, 2, 2];
const s = new Set(arr);
[...new Set('ababbc')].join('') // "abc"
add函数
这个add函数就是表示在数组中加入元素,如果你要加入的元素在原数组中有该元素就或添加失败。
let set = new Set();
let a = NaN;
let b = NaN;
set.add(a);
set.add(b);
console.log(set) // Set {NaN}
delete函数
这个就是删除某个值,返回一个布尔值,表示删除是否成功
const s = new Set();
s.add(1);
s.add(2);
s.add(2);
// 注意2被加入了两次,所以返回了两
s.size // 2
s.has(1) // true
s.has(2) // true
s.has(3) // false
s.delete(2);
s.has(2) // false
注意:
Array.from方法可以将 Set 结构转为数组。
const sets= new Set([1, 2, 3, 4, 5]);
const array = Array.from(sets);
这就提供了去除数组重复成员的另一种方法。
function dedupe(array) {
return Array.from(new Set(array));
}
dedupe([1, 1, 2, 3]) // [1, 2, 3]
Set结构的实例有四个遍历方法,可以用于遍历成员。
1、keys():返回键名的遍历器
2、values():返回键值的遍历器
3、entries():返回键值队的遍历器
4、forEach():使用回调函数遍历每个成员
set的遍历顺序就是插入顺序。
keys(),values(),entries()
这几个方法返回的都是遍历器的对象。由于set机构没有键名,只有键值,所以keys方法和values方法的行为完全一致。
entries方法返回的遍历器,同时包括键名和键值,所以每次输出一个数组,它的两个成员完全相等。
Set结构的实例默认可遍历,它的默认遍历器生成函数就是它的values方法。从而可以省略values方法,直接用for…of循环遍历Set。
下面的就是这几个方法遍历出来的结果:
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"]
forEach()
Set结构的实例玉数组一样,也拥有forEach方法,用于对每个成员执行某种操作,没有返回值。
forEach方法的参数就是一个处理函数。该函数的参数与数组的forEach一致,依次为键值、键名、集合本身。这里需要注意,Set 结构的键名就是键值,因此第一个参数与第二个参数的值永远都是一样的。
另外,forEach方法还可以有第二个参数,表示绑定处理函数内部的this对象。
let set = new Set([1, 4, 9]);
set.forEach((value, key) => console.log(key + ' : ' + value))
// 1 : 1
// 4 : 4
// 9 : 9
遍历的应用
扩展运算符(…)内部使用for…of循环,所以也可以用于 Set 结构。
let set = new Set(['red', 'green', 'blue']);
let arr = [...set];
// ['red', 'green', 'blue']
扩展运算符和 Set 结构相结合,就可以去除数组的重复成员。
let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)];
// [3, 5, 2]
而且,数组的map和filter方法也可以间接用于 Set 了。
let set = new Set([1, 2, 3]);
set = new Set([...set].map(x => x * 2));
// 返回Set结构:{2, 4, 6}
let set = new Set([1, 2, 3, 4, 5]);
set = new Set([...set].filter(x => (x % 2) == 0));
// 返回Set结构:{2, 4}
因此使用 Set 可以很容易地实现并集(Union)、交集(Intersect)和差集(Difference)。
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}
//差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}
如果想在遍历操作中,同步改变原来的 Set 结构,目前没有直接的方法,但有两种变通方法。一种是利用原 Set 结构映射出一个新的结构,然后赋值给原来的 Set 结构;另一种是利用Array.from方法。直接在遍历操作中改变原来的 Set 结构,如下:
// 方法一
let set = new Set([1, 2, 3]);
set = new Set([...set].map(val => val * 2));
// set的值是2, 4, 6
// 方法二
let set = new Set([1, 2, 3]);
set = new Set(Array.from(set, val => val * 2));
// set的值是2, 4, 6
在不使用set上的方法的情况下实现set的每个方法的功能及Set的原本的去重的功能,完整代码如下:
class MySet{
//可以直接传递参数,后需添加
constructor(iterator = []){
// 传递的内容必须是一个可迭代的对象
if(typeof iterator[Symbol.iterator] !== "function"){
throw new TypeError(`您所提供的${iterator}不是一个可迭代的对象`)
}
this._datas = [];
for(const item of iterator){
this.add(item)
}
}
//获取set集合的长度
get size(){
return this._datas.length;
}
//添加元素
add(data){
if(!this.has(data)){//如果是不重复的数据才可以添加否则是无效数据
this._datas.push(data)
}
}
//查询你要查找的元素
has(data){
for(const item of this._datas){
if(this.isEqual(data,item)){
return true;
}
}
}
//判断两个数据是否相等
isEqual(data1,data2){
if(data1 === 0 && data2 ===0){
return true;
}
return Object.is(data1,data2)
}
//清空set集合的
clear(){
this._datas.length = 0;
}
//遍历的方法
forEach(calllback){
for(const item of this._datas){
calllback(item,item,this)
}
}
delete(data){
for(let i = 0;this._datas.length;i++){
const element = this._datas[i];
if(this.isEqual(data,element)){
this._datas.splice(i,1);
return true
}
}
return false
}
*[Symbol.iterator]() {
for(const item of this._datas){
yield item;
}
}
}
Map集合
含义和基本用法
JavaScript 的对象,本质上是键值对的集合,但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。下面代码原意是将一个 DOM 节点作为对象data的键,但是由于对象只接受字符串作为键名,所以element被自动转为字符串[object HTMLDivElement]。
const data = {};
const ele = document.getElementById('myDiv');
data[ele] = 'metadata';
data['[object HTMLDivElement]'] // "metadata"
为了解决这个问题,ES6提供了Map数据结构。而Map集合它的键名不能一样,Map的参数传递一般为二维数组形式,数组的子数组里面只能有两项,第一项为键,第二项为值。Map集合的键名和键值是一一对应的。
Map集合上面的方法和Set上面的方法是一样的,只是多了两个方法然后没有add()方法,有两个方法,这两个方法就是set()方法和get()方法,使用方法如下:
//创建Map集合
const m = new Map();
//创建一个
const o = {p: 'Hello World'};
//如果键名一样你在添加同样的键名的value值就会覆盖前一个value值
m.set(o, 'content')//给Map集合中添加上一个key值为o的并且value的值为‘content’
m.get(o) //获取到key值为o的value,并且返回相应的value值,"content"
m.has(o) //查找Map集合中key值为o,返回true(成功)或false(失败)
m.delete(o) // 这个是删除Map集合中key值为o的数组,删除成功就返回为true,失败就返回为false
m.clear() //清空所有成员,没有返回值
如果读取一个未知的键,则返回undefined。
注意,只有对同一个对象的引用,Map 结构才将其视为同一个键。这一点要非常小心。
下面代码的set和get方法,表面是针对同一个键,但实际上这是两个不同的数组实例,内存地址是不一样的,因此get方法无法读取该键,返回undefined。
const map = new Map();
map.set(['a'], 555);
map.get(['a']) // undefined
遍历方法
Map的遍历器的方法和Set的遍历器的方法是一样的,需要特别注意的是,Map的遍历顺序就是插入顺序。
const map = new Map([
['F', 'no'],
['T', 'yes'],
]);
for (let key of map.keys()) {
console.log(key);
}
// "F"
// "T"
for (let value of map.values()) {
console.log(value);
}
// "no"
// "yes"
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"
// 或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
// 等同于使用map.entries()
for (let [key, value] of map) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
上面代码最后的那个例子,表示 Map 结构的默认遍历器接口(Symbol.iterator属性),就是entries方法。
map[Symbol.iterator] === map.entries // true
Map 结构转为数组结构,比较快速的方法是使用扩展运算符(…)。
const map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
[...map.keys()]// [1, 2, 3]
[...map.values()]// ['one', 'two', 'three']
[...map.entries()]// [[1,'one'], [2, 'two'], [3, 'three']]
[...map]// [[1,'one'], [2, 'two'], [3, 'three']]
结合数组的map方法、filter方法,可以实现 Map 的遍历和过滤(Map 本身没有map和filter方法)。
const map0 = new Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c');
const map1 = new Map(
[...map0].filter(([k, v]) => k < 3)
);// 产生 Map 结构 {1 => 'a', 2 => 'b'}
const map2 = new Map(
[...map0].map(([k, v]) => [k * 2, '_' + v])
);
// 产生 Map 结构 {2 => '_a', 4 => '_b', 6 => '_c'}
此外,Map 还有一个forEach方法,与数组的forEach方法类似,也可以实现遍历。
forEach方法还可以接受第二个参数,用来绑定this。
const reporter = {
report: function(key, value) {
console.log("Key: %s, Value: %s", key, value);
}
};
map.forEach(function(value, key, map) {
this.report(key, value);
}, reporter);
上面代码中,forEach方法的回调函数的this,就指向reporter。
与其他数据结构的互相转换
1、Map 转为数组
前面已经提过,Map 转为数组最方便的方法,就是使用扩展运算符(…)。
const myMap = new Map()
.set(true, 7)
.set({foo: 3}, ['abc']);
[...myMap]
// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
2、数组 转为 Map
将数组传入 Map 构造函数,就可以转为 Map。
new Map([
[true, 7],
[{foo: 3}, ['abc']]
])
// Map {
// true => 7,
// Object {foo: 3} => ['abc']
// }
3、Map 转为对象
如果所有 Map 的键都是字符串,它可以无损地转为对象。
function strMapToObj(strMap) {
let obj = Object.create(null);
for (let [k,v] of strMap) {
obj[k] = v;
}
return obj;
}
const myMap = new Map()
.set('yes', true)
.set('no', false);
strMapToObj(myMap)// { yes: true, no: false }
如果有非字符串的键名,那么这个键名会被转成字符串,再作为对象的键名。
4、对象转为 Map
对象转为 Map 可以通过Object.entries()。
let obj = {"a":1, "b":2};
let map = new Map(Object.entries(obj));
此外,也可以自己实现一个转换函数。
function objToStrMap(obj) {
let strMap = new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
return strMap;
}
objToStrMap({yes: true, no: false})
// Map {"yes" => true, "no" => false}
5、Map 转为 JSON
Map 转为 JSON 要区分两种情况。一种情况是,Map 的键名都是字符串,这时可以选择转为对象 JSON。
function strMapToJson(strMap) {
return JSON.stringify(strMapToObj(strMap));
}
let myMap = new Map().set('yes', true).set('no', false);
strMapToJson(myMap) // '{"yes":true,"no":false}'
另一种情况是,Map 的键名有非字符串,这时可以选择转为数组 JSON。
function mapToArrayJson(map) {
return JSON.stringify([...map]);
}
let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
mapToArrayJson(myMap) // '[[true,7],[{"foo":3},["abc"]]]'
6、JSON 转为 Map
JSON 转为 Map,正常情况下,所有键名都是字符串。
function jsonToStrMap(jsonStr) {
return objToStrMap(JSON.parse(jsonStr));
}
jsonToStrMap('{"yes": true, "no": false}') // Map {'yes' => true, 'no' => false}
但是,有一种特殊情况,整个 JSON 就是一个数组,且每个数组成员本身,又是一个有两个成员的数组。这时,它可以一一对应地转为 Map。这往往是 Map 转为数组 JSON 的逆操作。
function jsonToMap(jsonStr) {
return new Map(JSON.parse(jsonStr));
}
jsonToMap('[[true,7],[{"foo":3},["abc"]]]') // Map {true => 7, Object {foo: 3} => ['abc']}
在不使用Map上的方法的情况下实现Map的每个方法的功能及Set的原本的去重的功能,完整代码如下:
class myMap{
constructor(iterator = []){
if(typeof iterator[Symbol.iterator] !== "function"){
throw new TypeError(`您所提供的${iterator}不是一个可迭代的对象`)
}
this._datas = [];
for(const item of iterator){
//item也是可迭代的对象
if(typeof item[Symbol.iterator] !== "function"){
throw new TypeError(`您所提供的${item}不是一个可迭代的对象`)
}
const iter = item[Symbol.iterator]();
const key = iter.next().value;
const value = iter.next().value;
this.set(key,value)
}
}
//添加map集合添加数据的set方法
set(key,value){
const obj = this._getObj(key);
if(obj){
obj.value = value;
}else{
this._datas.push({
key,
value
})
}
}
_getObj(key){
for(const item of this._datas){
if(this.isEqual(item.key,key)){//键名重复
return item;
}
}
}
//判断键值是否重复的方法
isEqual(data1,data2){
if(data1 === 0 && data2 === 0){
return true;
}
return Object.is(data1,data2)
}
//获取数据的get方法
get(key){
const item = this._getObj(key);
if(item){
return item.value
}
return undefined;
}
//清空Map集合的方法
clear(){
this._datas.length = 0
}
//查看Map集合的长度
get size(){
return this._datas.length;
}
//判断你要查找的元素是否存在
has(key){
return this._getObj(key) !== undefined;
}
//删除根据key值来删除数据
delete(key){
for(let i =0;i<this._datas.length;i++){
const element = this._datas[i];
if(this.isEqual(element.key,key)){
this._datas.splice(i,1)
return true
}
}
return false;
}
//重写forEach方法
forEach(callback){
for(const item of this._datas){
callback(item.value,item.key,this)
}
}
*[Symbol.iterator](){
for(const item of this._datas){
yield [item.key,item.value]
}
}
}