作者:米书林
参考文章:《菜鸟教程》、《 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中的key
和value
被处理成数组的两个元素。
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]
工作中,若交集、并集都需要求,那适合用第一种方法,若只需要差集,那可以用第二种方式。