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