1. Map()构造函数
【意义】 实现了真正的数据一对一的功能,实现了健名可以是一个对象
1.1 对象的键名为对象时
当键名是一个对象,普通object不能实现键值一一对应,如下面的例子:
var m = {};
var x = {id: 1},
y = {id: 2};
m[x] = 'foo'; // -> 不能实现键值一一对应,输出结果如下图
m[y] = 'bar';
console.log(m); // 通过 Object.prototype.toString() 将健名转为 [Object object]
console.log(m[x]);
console.log(m[y]);
由上面的例子我们可知,当把一个对象作为健名时会默认调用 Object.prototype.toString()将健名转为 [Object object]
1.2 使用Map
【概念】 Map 实现键值一 一对应,与 对象类似的数据结构
Map本身也是一个构造函数,用来生成Map对象
let map = new Map();
【参数】
-
具备
iterator接口的数据结构,必须具有双元数组结构
双元数组:分别对应
key和value
【案例】
输出一个map:
let map = new Map([
// 双元数组
["name", "zhangsan"],
["title", "lisi"],
]);
console.log(map); // 输出结果如下图
等同于:
- 这里是用了
Map中的set方法来设置值
map.set('name', 'zhangsan');
map.set('title', 'lisi');
console.log(map);
模拟给参数的过程:
var items = [
['name', 'zhangsan'],
['title', 'lisi']
]
var m = new Map();
items.forEach(([key, value]) => m.set(key, value)); // 模式匹配
console.log(m);
1.3 操作Map的方法
与 Set 一样有 has、delete、clear方法 和 size(键值对个数)属性
【注意】
通过 set() 方法设置键名和键值,返回的也是当前实例,通过 get() 获取键值,取值时要保证是同一个引用,如下例子:
let m = new Map();
var x = {id: 1},
y = {id: 2};
m.set(x, 'foo');
m.set(y, 'bar');
/*
设置键值是一个对象
*/
console.log(m.get(x)); // foo
console.log(m.get(y)); // bar
console.log(m);
迭代的方法
1. keys()、values()、entries()
-
keys():返回键名的遍历器 -
values():返回键值的遍历器 -
entries():返回所有成员的遍历器
【参数】 需要具备 iterator 接口的数据结构
【特点】
-
for of 直接遍历 map,本质上是调用 entries 方法
-
m[Symbol.iterator] === m.entries
let m = new Map();
var x = {id: 1},
y = {id: 2};
m.set(x, 'foo');
m.set(y, 'bar');
// 三者输出如下图
console.log(m.keys());
console.log(m.values());
console.log(m.entries());
for (let keys of m.keys()) {
console.log(keys) // 输出 {id: 1} {id: 2}
}
for (let values of m.values()) {
console.log(values) // 输出 foo bar
}
for (let entries of m.entries()) {
console.log(entries)
}
// for of 直接遍历 map
// 本质上是调用 entries 方法
for (let [key, values] of m) {
console.log(key, values);
}
const map = new Map();
map.set("a", 1);
map.set("b", 2);
map.set("C", 3);
for (const iterator of map) {
console.log(iterator);
}
for (const [key, value] of map) {
console.log(key, value);
}
2. forEach()
-
遍历字典的所有成员
2. 特殊情况1:键名相同时
注意:
-
健名相同时
map中元素的值后一个会覆盖前一个 -
获取未定义的键值返回
undefined
const map = new Map();
map.set(1, 'foo');
map.set(1, 'bar');
console.log(map.get(1)); // 后一个覆盖前一个,输出 bar
map.set([5], 555); // 键名是引用值,只有指针地址是同一个,才会认为是同一个键名
console.log(map.get([5])); // undefined
let x = [777]
map.set(x, 777);
console.log(m.get(x)); // 此时是同一个引用值
// 对象同理
map.set({}, 555)
console.log(map.get({})); // undefined
3. 特殊情况2:原始值为键名时覆盖的问题
map 中 +0 === -0
map 中 get 的时候判断键名用的是===
map 中 NaN === NaN
const map = new Map();
map.set(-0, 123);
console.log(map.get(+0)); // 123
console.log( +0 === -0 ); // true
console.log(Object.is(+0, -0)); // false
map.set(true, 1);
map.set('true', 2);
console.log(map.get(true)); // 1 -> get的时候判断键名用的是 ===
map.set(undefined, 1);
map.set(null, 2); // undefined !== null
console.log(map.get(undefined)); // 1
console.log(map.get(null)); // 2
map.set(NaN, 123);
console.log(map.get(NaN)); // 123 ⭐ map 中 NaN===NaN
console.log(NaN === NaN); // false
console.log(Object.is(NaN, NaN)); // true
4. map常见用法
1. map转换为数组
【思路】 通过拓展运算符实现
拓展运算符可以将部署了迭代器接口的数据类型变为数组
const myMap = new Map();
myMap.set(true, 7).set(
{
foo: 3,
},
["abc"]
);
console.log(myMap);
// 拓展运算符可以将部署了迭代器接口的数据类型变为数组
console.log([...myMap]);
2. 数组转为map
必须是双源数组,数组里面的第一个元素作为键,第二个元素作为值
const map = new Map([
[true, 7],
[
{
foo: 3,
},
["abc"],
],
]);
console.log(map);
3. map转为对象(键名为字符串)
const myMap = new Map();
myMap.set(true, 7).set("a", "abc").set(
{
foo: 3,
},
["abc"]
);
function strMapToObj(strMap) {
let obj = Object.create(null);
for (let [key, value] of strMap) { // for of 遍历 map 本质是调用 entries 方法
console.log(key, value);
obj[key] = value;
}
return obj;
}
console.log(strMapToObj(myMap));
4. 对象转成map
function objToStrMap(obj) {
let map = new Map();
for (let key of Object.keys(obj)) {
map.set(key, obj[key]);
}
return map;
}
objToStrMap({
true: 7,
no: false
})
5. 字典
【概念】 字典就是基于 ES6 中的Map类的结构
【集合与字典的区别】
-
共同点:集合、字典可以存储不重复的值
-
不同点:集合是以
[值,值]的形式存储元素,字典是以[键,值]的形式存储
Map其实的主要用途也是用于存储数据的,相比于Object只提供“字符串—值”的对应,Map提供了“值—值”的对应。如果你需要“键值对”的数据结构,Map 比Object更合适
参考:htttps://juejin.cn/post/6844903589920374792
6. 对比
6.1 与set对比
- map 和 set 的方法基本一样
- 只是 map 多了存值和取值的方式,即 set和 get
- set 没有键名,所以存值只用 add
let m = new Map();
var x = {id: 1},
y = {id: 2};
m.set(x, 'foo');
console.log(m.set(y, 'bar'));
console.log(m.size);
console.log(m); // 删除前打印有实时性,和删除后打印一样
console.log(m.delete(x)); // 删除,返回的是布尔值
console.log(m.clear()); // 清空, 返回值是 undefined
// 判断键是否存在
console.log(m.has(x)); // 返回的是布尔值
6.2 map 和 array对比
let map = new Map();
let arr = new Array();
// 增
map.set('t', 1);
array.push({
't': 1
});
console.log(map, arr)
// 查
let map_exist = map.has('t');
let arr_exist = arr.find(item => item.t);
console.log(map_exist, arr_exist)
// 改
map.set('t', 2);
arr.forEach(item => item.t ? item.t = 2 : '');
console.log(map, arr);
// 删
map.delete('t');
let index = arr.findIndex(item => item.t);
arr.splice(index, 1);
console.log(map, arr)
6.3 set 和 array
set 中的值是唯一的
let set = new Set();
let arr = [];
// add
set.add({
t: 1
});
arr.push({
t: 1
});
console.log(set, arr);
// search
let obj = {
t: 1
};
let set_exist = set.has(obj); // 引用值需要被存储,否则找不到
let arr_exist = arr.find(item => item.t);
console.log(set_exist, arr_exist)
// modify
set.forEach(item => item.t ? item.t = 2 : '');
arr.forEach(item => item.t ? item.t = 2 : '');
console.log(set, arr);
// delete
set.forEach(item => item.t ? set.delete(item) : '');
let index = arr.findIndex(item => item.t);
arr.splice(index, 1);
console.log(map, arr)
6.4 map set object对比
let item = {t:1};
let map = new Map();
let set = new Set();
let obj = {};
// add
map.set('t', 1);
set.add(item);
obj['t'] = 1;
console.log(obj, map, set);
// search
console.log({
map_exist: map.has('t'),
set_exist: set.has(item),
obj_exist: 't' in obj,
obj_exist1: obj.hasOwnProperty('t')
})
// modify
map.set('t', 2);
item.t = 2;
obj['t'] = 2;
// delete
map.delete('t');
set.delete(item);
delete obj['t'];
map 与 object 对比
Tip:ES2015 建议浏览器厂商对对象的枚举采取有序化操作
-
Map 健名可以是引用值或基本类型, Object 健名只能是基本类
-
Map 有序,可迭代;Object 无序,不可迭代
-
Map 可以用 size 属性记录长度,Object 没有相关属性记录(可以通过
Object.keys().length) -
Map 可
for of,支持[Symbol.iterator],Object 不支持 -
Map 没有序列化操作(
Json.stringify/parse),Object 支持
const map = new Map();
map.set("a", 1);
map.set("b", 2);
map.set("C", 3);
for (const [key, value] of map) {
console.log(key, value);
}
const obj = {
a: 1,
b: 2,
c: 3
}
for (let key in obj) {
console.log([key, obj[key]]);
}
上面两个例子输出的结果是一致的,但是要做一个区分⬇️
迭代、枚举、遍历:
-
遍历要在迭代的基础上,迭代是不连续的,每一次的迭代加起来叫做遍历(每次拿一个东西)
-
对象 使用
for in每次取他的 key 和 value 的过程叫做枚举, -
枚举 和 遍历 最大的区别是无序和有序的区别,
Map 实现序列化:
function replacer(key, value) {
console.log(key, value);
if (value instanceof Map) {
return {
type: "Map",
value: [...value],
};
} else {
return value;
}
}
function reviver(key, value) {
if (value.type === "Map") {
return new Map(value.value);
}
return value;
}
const map = new Map();
map.set("a", 1);
map.set("b", 2);
map.set({ a: 1 }, 3);
map.set([1, 2, 3], 4);
const stringRes = JSON.stringify(map, replacer);
const mapRes = JSON.parse(stringRes, reviver);
console.log(stringRes);
console.log(mapRes);