Map

63 阅读6分钟

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 接口的数据结构,必须具有双元数组结构

双元数组:分别对应 keyvalue

【案例】

输出一个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提供了“值—值”的对应。如果你需要“键值对”的数据结构,MapObject更合适

参考: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 建议浏览器厂商对对象的枚举采取有序化操作

  1. Map 健名可以是引用值或基本类型, Object 健名只能是基本类

  2. Map 有序,可迭代;Object 无序,不可迭代

  3. Map 可以用 size 属性记录长度,Object 没有相关属性记录(可以通过 Object.keys().length

  4. Map 可for of,支持[Symbol.iterator],Object 不支持

  5. 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);