ES6—Map和Set数据结构

作者:米书林
参考文章:《菜鸟教程》、《 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]

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350