ES6—Map和Set数据结构

1,160 阅读12分钟

作者:米书林 参考文章:《菜鸟教程》、《 ECMAScript 6 入门》(阮一峰)

Map对象

Map对象简介

作用:Map对象用于保存键值对,它的键可以为任意类型的数据,常用于建立数据的映射关系。

Map对象和Object对象的对比

相同点:

-它们都是对象数据类型 -它们都是以键值对的形式存储数据

不同点:

-Object对象的键只能是字符串或Symbol,Map对象的键可以是任意数据类型(可以看作是值-值的关系) -Map 中的键值是有序的(FIFO 原则),而添加到Object对象中的键则不是 -Map 的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算 -Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突

Map 中的 key

key是字符串

用法: Map()是一个构造函数,需要用new来实例化,Map()构造函数貌似只能接收一个二维数组的参数null和undefined(至少目前遇到的是这样),其他类型参数会报错

// Map()可以接收一个二维数组,二维数组中取arr[][0]为key,arr[][2]为value,多余的数组元素会被忽略
let map = new Map([[1,20]]);
// Map(1) {1 => 20}
let map1 = new Map([[1,20,30]]);
// Map(1) {1 => 20}

// Map()可以接收null作为参数
let map2 = new Map(null);  // Map(0) {}

// Map()可以接收undefined作为参数
let map3 = new Map(undefined);  // Map(0) {}

注意:null和undefined作为参数时相当于不传参数,即直接new Map()

let smap = new Map();
// set设置Map值
smap.set("one",1);
// get获取Map键对应的值
smap.get("one");

应用场景:

1.处理英文缩写和汉语对应关系,下面以星期为例

function eToC(str){
  // 实例化一个Map
  let wmap = new Map();
  // 使用实例化的Map存储具有对应关系的数据
  wmap.set("Mon","星期一");
  wmap.set("Tue","星期二");
  wmap.set("Wed","星期三");
  wmap.set("Thur","星期四");
  wmap.set("Fri","星期五");
  wmap.set("Sat","星期六");
  wmap.set("Sun","星期日");
  // 根据查询字符串,返回对应数据
  return wmap.get(str);
}

// 直接调用
eToC("Thur");   // "星期四"
// 获取当前时间后调用
let nowDate = (new Date()).toString();
let end = nowDate.indexOf(" ");
let week = nowDate.slice(0,end);
eToC(week);    // "星期三"

上面的方法来获取星期显然很笨拙,因为我们需要处理字符串,实际上我们可以通过getDay()获取到一个0~6的数字,然后再通过Map来对应,具体方法见key为数字小节。

2.模拟简单的自动回复机器人

因为对话机器人我们不需要作特别复杂的逻辑处理,以为都是使用判断添加来处理,但现在来看,好像Map结构数据更适合,我们还是直接来看代码吧。

// 呆板机器人回复
function ack(str){
  let ack_map = new Map();
  ack_map.set("你好","你好啊!");
  ack_map.set("今天天气真不错","是啊,很适合出门!");
  ack_map.set("吃饭了没","还没呢,你呢?");
  ack_map.set("吃啦","我还有点事,先走了,拜拜!!");
  ack_map.set("没吃呢","那赶快去吃吧,我还有点事,先走了,拜拜!!");
  // 能匹配到就返回
  if(ack_map.get(str)) return ack_map.get(str);
  // 匹配不到就返回一个提示
  else return "对不起,你说的话我听不懂"
}

上面代码只要调用函数就能简单的返回一些数据了,当然啦,工作上的自动回复机器人这么笨是会被老板开除的,我们要做一个稍微复杂机器人是少不了正则匹配和数组的,这个可以自己去探究。

#####key是对象

var myMap = new Map();
var keyObj = {}, 
 
myMap.set(keyObj, "和键 keyObj 关联的值");
myMap.get(keyObj); // "和键 keyObj 关联的值"
myMap.get({}); // undefined, 因为 keyObj !== {}

key是函数

var myMap = new Map();
var keyFunc = function () {}, // 函数
 
myMap.set(keyFunc, "和键 keyFunc 关联的值");
 
myMap.get(keyFunc); // "和键 keyFunc 关联的值"
myMap.get(function() {}) // undefined, 因为 keyFunc !== function () {}

key 是 NaN

var myMap = new Map();
myMap.set(NaN, "not a number");
 
myMap.get(NaN); // "not a number"
 
var otherNaN = Number("foo");
myMap.get(otherNaN); // "not a number"

虽然 NaN 和任何值甚至和自己都不相等(NaN !== NaN 返回true),NaN作为Map的键来说是没有区别的。 ####key 是 数组

let map = new Map([
  ['name', '张三'],
  ['age', 23],
]);

map.size // 2
map.has('name') // true
map.get('name') // "张三"
map.has('age') // true
map.get('age') // 23

Map的key可以是任何类型的值,包括正则等;

要点
1.Map的key可以为任意类型的值;
2.new一个Map就是新实例化了一个Map;
3.对同一个key值进行多次赋值,后面的会覆盖前面的;
4.未被set的Map关键字使用get方法会返回`undefined`;
5.Map 的键是跟内存地址绑定的,引用类型数据需要指向同一个地址才是同一个键;
6.原始数据类型,只要他们的值严格相等,Map 将其视为一个键;
7.NaN不严格相等于自身,但 Map 将其视为同一个键;
8.undefined和null是两个不同的键
// new一个Map就是新实例化了一个Map
let myMap1 = new Map();
let myMap2 = new Map();
myMap1 === myMap2 ;  // false

// 对同一个key值进行多次赋值,后面的会覆盖前面的;
let myMap = new Map();
myMap.set("a", 'a')
myMap.get("a") // "a"
myMap.set("a", 'aa');
myMap.get("a") // "aa"

// 未被set的Map关键字使用get方法会返回undefined;
let myMap3 = new Map();
myMap3.get("b");  // undefined

// Map 的键是跟内存地址绑定的,引用类型数据需要指向同一个地址才是同一个键
let myMap4 = new Map();
myMap4 .set(['12'], 12);
myMap4 .get(['12']) // undefined
myMap4 .set({name:"abc"}, "abc");
myMap4 .get({name:"abc"}) // undefined
let obj = {name:"abc"}
myMap4 .set(obj , "abc");
myMap4 .get(obj) // "abc"

// 原始数据类型,只要他们的值严格相等,Map 将其视为一个键;
let map = new Map();
map.set(-0, 123);
map.get(+0) // 123

map.set(true, 1);
map.set('true', 2);
map.get(true) // 1
// undefined和null是两个不同的键
map.set(undefined, 3);
map.set(null, 4);
map.get(undefined) // 3
// NaN不严格相等于自身,但 Map 将其视为同一个键;
map.set(NaN, 123);
map.get(NaN) // 123
// 
Map的常用方法

1.set()

set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。

let myMap = new Map()
  .set(60, '及格')
  .set(80, '良')
  .set(90, '优秀')
  .set(100, '非常优秀');

注意:set方法返回的是当前的Map对象,因此可以采用链式写法。

2.get()

get方法读取key对应的键值,如果找不到key,返回undefined

let myMap = new Map()
  .set(60, '及格')
  .set(80, '良')
  .set(90, '优秀')
  .set(100, '非常优秀');
myMap.get(90); // "优秀"
myMap.get(59); // undefined

3.has()

has方法判断某个键是否在当前 Map 对象之中,返回一个布尔值。

let myMap = new Map()
  .set(60, '及格')
  .set(80, '良')
  .set(90, '优秀')
  .set(100, '非常优秀');
myMap.has(90); // true
myMap.has(30); // false

4.delete()

delete方法用于删除某个键,删除成功返回true,删除失败(不存在的键)返回false。

let myMap = new Map()
  .set(60, '及格')
  .set(80, '良')
  .set(90, '优秀')
  .set(100, '非常优秀');
myMap.delete(90);  // true
myMap.has(90);  // false
myMap.delete(90);  // false

5.clear()

clear方法清除所有成员

let myMap = new Map()
  .set(60, '及格')
  .set(80, '良')
  .set(90, '优秀')
  .set(100, '非常优秀');
myMap.has(60);   // true
myMap.clear();  
myMap.has(60);  // false
Map的属性

1.size

size属性返回 Map 结构的成员总数

let myMap = new Map()
  .set(60, '及格')
  .set(80, '良')
  .set(90, '优秀')
  .set(100, '非常优秀');
myMap.size;   // 4

Map的遍历(迭代)

for...of

Map 结构原生提供三个遍历器生成函数: -Map.prototype.keys():返回键名的遍历器。 -Map.prototype.values():返回键值的遍历器。 -Map.prototype.entries():返回所有成员的遍历器。

let myMap = new Map()
  .set(60, '及格')
  .set(80, '良')
  .set(90, '优秀')
  .set(100, '非常优秀');
 // 遍历myMap
for (var [key, value] of myMap) {
  console.log(key + " = " + value);
}
// 结果:
//   60 = 及格
//   80 = 良
//   90 = 优秀
//   100 = 非常优秀

for (var [key, value] of myMap.entries()) {
  console.log(key + " = " + value);
}
/* 这个 entries 方法返回一个新的 Iterator 对象,它按插入顺序包含了 Map 对象中每个元素的 [key, value] 数组。 */
// 结果:
//   60 = 及格
//   80 = 良
//   90 = 优秀
//   100 = 非常优秀
 
for (var key of myMap.keys()) {
  console.log(key);
}
/* 这个 keys 方法返回一个新的 Iterator 对象, 它按插入顺序包含了 Map 对象中每个元素的键。 */
// 结果:
//   60
//   80
//   90 
//   100 
 
for (var value of myMap.values()) {
  console.log(value);
}
/* 这个 values 方法返回一个新的 Iterator 对象,它按插入顺序包含了 Map 对象中每个元素的值。 */
// 结果:
//   及格
//   良
//   优秀
//   非常优秀
forEach()
let myMap = new Map()
  .set(60, '及格')
  .set(80, '良')
  .set(90, '优秀')
  .set(100, '非常优秀');
myMap.forEach(function(value, key) {
  console.log(key + " = " + value);
}, myMap)
// 执行结果
// 60 = 及格
// 80 = 良
// 90 = 优秀
//100 = 非常优秀

Map 结构的操作

1.Map 转为数组

Map结构可以通过扩展运算符(...)转为数组

let myMap = new Map()
  .set(60, '及格')
  .set(80, '良')
  .set(90, '优秀')
  .set(100, '非常优秀');
[...myMap] 
// 返回:
[[60, "及格"],[80, "良"],[90, "优秀"],[100, "非常优秀"]]

返回的是一个二维数组,Map中的keyvalue被处理成数组的两个元素。

Map 的克隆

var myMap1 = new Map([["key1", "value1"], ["key2", "value2"]]);
var myMap2 = new Map(myMap1);
console.log(myMap1 === myMap2 ); 
// 打印 false。 Map 对象构造函数生成实例,迭代出新的对象。

Map 的合并

var myMap1  = new Map([[1, 'one'], [2, 'two'], [3, 'three'],]);
var myMap2 = new Map([[1, 'uno'], [2, 'dos']]);
 
// 合并两个 Map 对象时,如果有重复的键值,则后面的会覆盖前面的,对应值即 uno,dos, three
var myMap = new Map([...myMap1, ...myMap2]);
console.log(myMap);
// Map(3) {1 => "uno", 2 => "dos", 3 => "three"}

Set结构

Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。

Set要点

-Set允许存储任何类型的唯一值,但Set()不能接收Object、Boolean和NaN,使用add()方法可添加任意类型值; -向 Set 加入值的时候,不会发生类型转换,所以123和"123"是两个不同的值; -Set 内部使用“===”来判断两个值是否相等,但是NaN是个特例 -引用类型得地址指向相同才是唯一值,Symbol得用变量存储才能被has()取到 -+0 与 -0 在存储判断唯一性的时候是恒等的,所以不重复; -undefined 与 undefined 是恒等的,所以不重复; -Set 结构的键名就是键值

基本用法:

Set()是一个构造函数,需要用new来实例化,Set()构造函数可以接收一个具有 iterable 接口的其他数据结构(如数组,Map和Set结构等数据),其他类型会报错

// Set()可以接收一个数组参数
let set1 = new Set([1]);

// Set()可以接收一个Map结构数据参数
let map = new Map([[1,"one"]])
let set2 = new Set(map);

// Set()可以接收一个Set结构数据参数
let set3 = new Set([1]);
let set4 = new Set(map);

// Set()可以接收一个字符串作为参数,相当于将字符串使用split()方法后再传入Set()
let set10 = new Set("abc");
// Set(3) {"a", "b", "c"}

// Set()可以接收一个null作为参数
let set5 = new Set(null);  // Set(0) {}

// Set()可以接收一个undefined作为参数
let set6 = new Set(undefined); // // Set(0) {}

// Set()不能接收一个NaN作为参数
let set7 = new Set(NaN);
// Uncaught TypeError: number NaN is not iterable


// Set()不能接收一个Object作为参数
let set8 = new Set({});
// Uncaught TypeError: object is not iterable

// Set()不能接收一个Bollean作为参数
let set9 = new Set(true);
// Uncaught TypeError: boolean true is not iterable

注意:null和undefined作为参数时相当于不传参数,即直接new Set()

Set的属性

Set.prototype.constructor:构造函数,默认就是Set函数。 Set.prototype.size:返回Set实例的成员总数。

let set = new Set([1,2,3,4]);
set.constructor;    //  ƒ Set() { [native code] }
set.size;   // 4

Set的方法

Set.prototype.add(value):添加某个值,返回 Set 结构本身。 Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。 Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。 Set.prototype.clear():清除所有成员,没有返回值。

let set1 = new Set();
// set的add方法可以添加任何类型的数据
// 添加数字
set1.add(1);
// 添加字符串
set1.add("abc");
// 添加布尔值true
set1.add(true);
// 添加布尔值false
set1.add("abc");
// 添加null
set1.add(null);
// 添加undefined
set1.add(undefined);
// 添加NaN
set1.add(NaN);
// 添加数组
set1.add([1,2,3]);
// 添加对象
set1.add({name:"张三"})
// 添加Symbol数据
set1.add(Symbol("123"));
// 添加Map数据
set1.add(new Map([[1,"one"]]));
// 添加Set数据
set1.add(new Set([1]));
// 添加函数
set1.add(function add(){});
console.log(set1);
// 结果为:Set(12) {1, "abc", true, null, undefined, …}

// has()检测是否含有某个Set成员
set1.has(1);  // true
set1.has(NaN);  // true  NaN不严格相等,但是在Set中它是一个唯一值,即能直接用NaN检索到
set1.has(Symbol("123"));  // false
// Symbol类型得通过变量定义才能被检测到

// delete()删除某个Set成员
set1.delete(NaN); 
set1.has(NaN);  // false

set1.has(undefined);  // true
set1.clear();
set1.has(undefined);   // false

Set的遍历(迭代)

for...of

Set结构原生提供三个遍历器生成函数: -Set.prototype.keys():返回键名的遍历器。 -Set.prototype.values():返回键值的遍历器。 -Set.prototype.entries():返回所有成员的遍历器。

let set = new Set(['red', 123, true,null]);

for (let item of set.keys()) {
  console.log(item);
}
// red
// 123
// true
// null

for (let item of set.values()) {
  console.log(item);
}
// red
// 123
// true
// null

for (let item of set.entries()) {
  console.log(item);
}
// ["red", "red"]
// [123, 123]
// [true, true]
// [null, null]

set.entries()方法返回的遍历器,同时包括键名和键值,所以每次输出一个数组,它的两个成员完全相等。

Set 结构的实例默认可遍历,它的默认遍历器生成函数就是它的values方法,因此可以省略values方法,直接用for...of循环遍历 Set。

let set = new Set(['red', 123, true,null]);
for (let item of set) {
  console.log(item);
}
// red
// 123
// true
// null
forEach()
let set = new Set(['red', 123, true,null]);
set.forEach((value, key) => console.log(key + ' : ' + value))
// red : red
// 123 : 123
// true : true
// null : null

Set的应用场景

数组去重
let arr = [1,1,2,3,3,2,4,5,4];
let set = new Set(arr);
console.log(arr); // [1,1,2,3,3,2,4,5,4]
arr = [...set];  
console.log(arr); // [1, 2, 3, 4, 5]
字符串去重
let str= "aabbccddab";
let set = new Set(str);
console.log(str); //  "aabbccddab"
str= [...set].join("");  
console.log(str); 
// "abcd"
求数组并集
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
let set= new Set([...a, ...b]);
let union = [...set];
console.log(union);   // [1, 2, 3, 4]
求数组交集
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
let set= new Set([...a].filter(x => b.has(x))); 
let intersect = [...set];
console.log(intersect );   // [ 2, 3]
求数组差集
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);

// 方法一:先求并集、交集
// 先求出并集
let set = new Set([...a, ...b]);
// 再求出交集
let set1= new Set([...a].filter(x => b.has(x))); 
// 再利用并集求差集
let set2 = new Set([...set].filter(x => !set1.has(x))); 
let difference = [...set2];
console.log(difference);   // [1, 4]

// 方法二:分别求差集,再求并集
let set3=new Set([...[...a].filter(x => !b.has(x)),...[...b].filter(x => !a.has(x))]);
let difference1 = [...set3]  // [1, 4]

工作中,若交集、并集都需要求,那适合用第一种方法,若只需要差集,那可以用第二种方式。