Map 对象保存键值对。任何值(对象或者原始值)都可以作为一个键或一个值。
它和 Object 对象不同,对象只能用字符串和 Symbol 作为键,而 Map 可以使用任何值。
new Map([iterable]);
参数 | 说明 |
---|---|
iterable | Iterable 可以是一个数组或者其他 Iterable 对象,其元素或为键值对,或为两个元素的数组。 每个键值对都会添加到新的 Map。null 会被当做 undefined 。 |
键的比较是基于 SameValueZero 算法:NaN
是与 NaN
相同的(虽然 NaN !== NaN
),剩下所有其它的值是根据 ===
运算符的结果判断是否相等。
对象与字典的对比
Object 和 Map 类似的一点是,它们都允许你按键存取一个值,都可以删除键,还可以检测一个键是否绑定了值。
除了键类型上的不同,Map 和 Object 还有以下不同:
size
获取键值个数,Object 的键值对个数只能手动计算map = Object.create(null)
来创建一个没有原型的对象,但是这种用法不太常见但是这并不意味着你可以随意使用 Map,对象仍旧是最常用的。Map 实例只适合用于集合(Collections),你应当考虑修改你原来的代码——先前使用对象来处理集合的地方。对象应该用其字段和方法来作为记录的。 如果你不确定要使用哪个,请思考下面的问题:
假如以上全是"是"的话,那么你需要用 Map 来保存这个集。相反,你有固定数目的键值对,独立操作它们,区分它们的用法,那么你需要的是对象。
属性 | 描述 |
---|---|
Map.prototype.constructor | 返回一个函数,它创建了实例的原型。默认是 Map 函数。 |
Map.prototype.size | 返回 Map 对象的键/值对的数量。 |
Map.prototype.size
属性返回 Map
结构的成员总数。
const map = new Map();map.set('foo', true);map.set('bear', false);console.log(map.size);// 2
方法 | 描述 |
---|---|
Map.prototype.set(key, value) | 用于设置 Map 对象中键的值,返回该 Map 对象 |
Map.prototype.get(key) | 用于获取一个 Map 对象中指定 key 的元素 |
Map.prototype.has(key) | 用于校验 Map 中是否存在指定 key 值的元素 |
Map.prototype.delete(key) | 用于移除任何与键相关联的值,并且返回该值 |
Map.prototype.clear() | 用于移除 Map 对象中的所有元素 |
Map.prototype.forEach(callbackFn [,thisArg]) | 按插入顺序,为 Map 对象里的每一键值对调用一次回调函数。如果为 forEach 提供了 thisArg ,它将在每次调用回调中作为 this 值 |
Map.prototype.keys() | 返回一个新的 Iterable 对象。它包含按照顺序插入 Map 对象中每个元素的 key 值。 |
Map.prototype.values() | 返回一个新的 Iterator 对象,它按插入顺序包含了 Map 对象中每个元素的值。 |
Map.prototype.entries() | 返回一个新的包含 [key, value] 对的 Iterable 对象,返回迭代器的迭代顺序与 Map 对象的插入顺序相同。 |
Map.prototype[@@iterator]() | 返回一个新的 MapIterator 对象,它按插入顺序包含了 Map 对象中每个元素的 [key, value] 数组。 |
set
方法设置 key
对应的键值,然后返回整个 Map
结构。如果 Map
结构。如果 key
已经有值,则键值会被更新,否则就新生成该键。
set
方法返回的是当前的 Map 对象,因此可以采用链式写法。
const map = new Map();// 键是字符串map.set('edition', 6);// 键是数值map.set(262, 'standard');// 键是 undefinedmap.set(undefined, 'nah');const m = new Map().set(1, 'a').set(2, 'b').set(3, 'c');
get
方法读取 key
对应的键值,如果找不到 key
,则返回 undefined
。
const m = new Map();const hello = function () {console.log('hello');};m.set(hello, 'Hello ES6!');// 键是函数m.get(hello);// Hello ES6!
has
方法返回一个布尔值,表示某个键是否在 Map 数据结构中。
const m = new Map();m.set('edition', 6);m.set(262, 'standard');m.set(undefined, 'nah');m.has('edition');// truem.has('years');// falsem.has(262);// truem.has(undefined);// true
delete
方法删除某个键,返回 true
。如果删除失败,则返回 false
。
const m = new Map();m.set(undefined, 'nah');m.has(undefined);// truem.delete(undefined);m.has(undefined);// false
clear
方法清除所有成员,没有返回值。
const map = new Map();map.set('foo', true);map.set('bar', false);map.size;// 2map.clear();map.size;// 0
入参首位为 value
值,其次为 key
键值。
const map = new Map();map.set('1', { a: 1 });map.set('2', { b: 2 });map.set('3', { c: 3 });map.forEach((value, key) => {console.log(key, value);// 1 { a: 1 }// 2 { b: 2 }// 3 { c: 3 }});
const map = new Map();map.set('1', { a: 1 });map.set('2', { b: 2 });map.set('3', { c: 3 });const keys = map.keys();for (const key of keys) {console.log(key);// 通过 map.get(key) 可得 value 值// 1// 2// 3}
const map = new Map();map.set('1', { a: 1 });map.set('2', { b: 2 });map.set('3', { c: 3 });const values = map.values();for (const value of values) {console.log(value);// { a: 1 }// { b: 2 }// { c: 3 }}
const map = new Map();map.set('1', { a: 1 });map.set('2', { b: 2 });map.set('3', { c: 3 });const entries = map.entries();for ([key, value] of entries) {console.log(key, value);// 1 { a: 1 }// 2 { b: 2 }// 3 { c: 3 }}
const map = new Map();map.set('1', { a: 1 });map.set('2', { b: 2 });map.set('3', { c: 3 });const keys = map.keys();for (i = 0; i < map.size; i++) {key = keys.next().value;console.log(key);// 1// 2// 3}const values = map.values();for (i = 0; i < map.size; i++) {value = values.next().value;console.log(value);// { a: 1 }// { b: 2 }// { c: 3 }}const entries = map.entries();for (i = 0; i < map.size; i++) {entry = entries.next().value;console.log(entry[0], entry[1]);// 1 { a: 1 }// 2 { b: 2 }// 3 { c: 3 }}
MapIterator
对象每次遍历下个元素都会调用 next()
,一次遍历完成之后,位置并不会复原。所以多次遍历必须用对应的 map
方法(keys()
、values()
和 entries()
)重新获取 Map Iterator
对象。若不重新获取,则迭代器无返回值。
Map 键与内存地址绑定。
⚠️ 注意:只有对同一个对象的引用, Map 结构才将其视为同一个键。
const map = new Map();map.set(['a'], 555);map.get(['a']);// undefined
上面的 set
方法和 get
方法表面上是针对同一个键,实际上却是两个值,这是因为数组引用的内存地址不一样导致的。因此 get
方法无法读取该值,返回 undefined
。
同样的值的两个实例在 Map 结构中被视为两个键。
const map = new Map();const a = ['foo'];const b = ['foo'];map.set(a, 123).set(b, 456);map.get(a);// 123map.get(b);// 456
Map 的键实际上是和内存地址绑定的,只要内存地址不一样,就视为两个键。这就解决了 同名属性碰撞(Clash) 的问题,我们扩展别人的库时,如果使用对象作为键名,不用担心自己的属性与原作者的属性同名。
如果 Map 的键是一个基本类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 就将其视为一个键,包括 0
和 -0
。
另外,需要注意得失,虽然 NaN
不严格等于自身,但 Map 将其视为同一个键。
const map = new Map();map.set(-0, 123);map.get(-0);// 123map.set(true, 1);map.set('true', 2);map.get(true);// 1map.set(undefined, 3);map.set(null, 4);map.get(undefined);// 3map.set(NaN, 123);map.get(NaN);// 123
以 Set 作为参数
const payload = new Set([['foo', 1],['bar', 2],]);const map = new Map(payload);map.get('foo');// 1
以 Map 作为参数
const payload = new Map([['baz'], 3]);const map = new Map(payload);map.get('baz');// 3
如果对同一个键多次赋值,后面的值会将覆盖前面的值。
const map = new Map();map.set(1, 'foo').set(1, 'baz');map.get(1);// 'baz'
NaN
也可以作为 Map 对象的键,虽然 NaN
和任何值甚至和自己都不相等(NaN !== NaN
返回 true
),但下面的例子表明,两个 NaN
作为 Map 的键来说是没有区别的。
const map = new Map();map.set(NaN, 'Not a number');map.get(NaN);// 'Not a number'const otherNaN = Number('foo');map.get(otherNaN);// 'Not a number'
合并两个 Map 实例对象时,如果有重复的键值,则后面的会覆盖前面的。
扩展运算符本质上是将 Map 对象转换成数组。
const first = new Map([[1, 'one'],[2, 'two'],[3, 'three'],]);const second = new Map([[1, 'uno'],[2, 'dos'],]);const merged = new Map([...first, ...second]);
Map 转为数组最方便的方法就是使用扩展运算符。
const map = new Map().set(true, 1).set({ foo: 2 }, ['abc']);console.log([...map]);// [ [true, 1], [ { foo: 2}, ['abc'] ] ]
将数组传入 Map 构造函数就可以转为 Map。
const m = new Map([[true, 7],[{ foo: 3 }, ['abc']],]);console.log(m);// Map {// true => 7,// Object {foo: 3} => ['abc']// }
如果 Map 的所有键都是字符串,则可以转为对象。
function toObject(strMap) {let o = Object.create(null);for (let [k, v] of strMap) {o[k] = v;}return o;}const m = new Map().set('yes', true).set('no', false);console.log(toObject(m));// {'yes': true, 'no': false}
function toMap(obj) {let strMap = new Map();for (let k of Object.keys(obj)) {strMap.set(k, obj[k]);}return strMap;}const m = toMap({ yes: true, no: false });console.log(m);// Map {"yes" => true, "no" => false}
Map 转为 JSON 要区分两种情况。一种情况是, Map 的键名都是字符串,这时可以选择转为对象 JSON。
const toJSON = (strMap) => JSON.stringify(toObject(strMap));let m = new Map().set('yes', true).set('no', false);console.log(toJSON(m));// '{"yes": true, "no": false}'
另一种情况是,Map 的键名有非字符串,这时可以选择转为数组 JSON。
const toArrayJSON = (map) => JSON.stringify([...map]);let m = new Map().set(true, 1).set({ foo: 2 }, ['abc']);console.log(toArrayJSON(m));// '[[true], 1], [{'foo': 2}, ['abc']]'
const toMap = (jsonStr) => toMap(JSON.parse(jsonStr));console.log(toMap('{"yes": true, "no": false}'));// Map {'yes' => true, 'no': false}
但是,有一种特殊情况:整个 JSON 就是一个数组,且每个数组成员本身又是一个具有两个成员的数组。这时,它可以一一对应地转为 Map 。这往往是数组转为 JSON 的逆操作。
const toMap = (jsonStr) => new Map(JSON.parse(jsonStr));console.log(toMap('[[true, 7], [{"foo": 3}, ["abc"]]]'));// Map(true => 7, Object {foo: 3} => ['abc'])
const timemap = new Map([[0, '星期天'],[1, '星期一'],[2, '星期二'],[3, '星期三'],[4, '星期四'],[5, '星期五'],[6, '星期六'],]);// 中间使用 React Hooks 的 useEffect 实现const [time, setTime] = setState(new Date());useEffect(() => {clearInterval();setInterval(() => {setTimeout(new Date());}, 1000);});const res = (timemap.get(time.getDay()) || '') + time.toLacleTimeString();