Map对象
Map的mdn文档地址
一、描述
1、概念及特性
- Map 对象是键值对的集合。
- 键:唯一。 Map 中的一个键只能出现一次;它在 Map 的集合中是独一无二的。
- 迭代: Map 对象按键值对迭代。 一个
for...of
循环在每次迭代后会返回一个形式为[key, value]
的数组。迭代按插入顺序进行,即键值对按 set() 方法首次插入到集合中的顺序(也就是说,当调用set()
时,map 中没有具有相同值的键)进行迭代。
代码及结果如下:
let mapObj=new Map()
mapObj.set('theFirstObj','theFirstValue');
mapObj.set('theSecondObj','theSecondValue');
mapObj.set('theThirdObj','theThirdValue');
for(var i of mapObj){
console.log(i)//返回一个形式为 [key, value] 的数组
}
2、键的相等
键的比较基于零值相等算法。(它曾经使用同值相等,将 0 和 -0 视为不同。检查浏览器兼容性。)这意味着
NaN
是与NaN
相等的(虽然NaN !== NaN
),剩下所有其他的值是根据===
运算符的结果判断是否相等。
2.1 关于零值相等:
类似于同值相等,但
+0 和 -0
被视为相等。
将NaN
视作是相等的
零值相等不作为 JavaScript API 公开,但可以通过自定义代码实现:
function sameValueZero(x, y) {
if (typeof x === "number" && typeof y === "number") {
// x 和 y 相等(可能是 -0 和 0)或它们都是 NaN
return x === y || (x !== x && y !== y);
}
return x === y;
}
console.log(sameValueZero(NaN,NaN),NaN==NaN,NaN===NaN)
零值相等在搜索期间通常具有最实用的行为,特别是在与
NaN
一起使用时。它被用于Array.prototype.includes()、TypedArray.prototype.includes() 及 Map 和 Set 方法用来比较键的相等性
。
3、Object 和 Map 的比较
Object
和 Map
类似的是,它们都允许你按键存取一个值、删除键、检测一个键是否绑定了值。
3.1关于默认键/意外的键
Map
默认不包含任何键。它只包含显式存入的键值对。Object
有原型,因此它包含默认的键,如果不小心的话,它们可能会与你自己的键相冲突。(备注:这可以通过使用Object.create(null)
来绕过,但很少这样做。)
3.2键的类型
Map
的键可以为 任意值(包括函数、对象或任何原始值)。Object
的键必须为String
或 Symbol。
3.3键的顺序
Map
中的键以简单、直接的方式排序(FIFO 原则):Map 对象按照插入的顺序迭代条目、键和值。Object
不像Map那样有固定的顺序
尽管现在普通的
Object
的键是有序的,但情况并非总是如此,并且其排序比较复杂的。
因此,最好不要依赖属性的顺序。
该顺序最初仅在 ECMAScript 2015 中为自有属性定义;ECMAScript 2020 还定义了继承属性的顺序。
但请注意,没有单一机制可以迭代对象的所有属性;各种机制各自包含不同的属性子集。
(for-in
仅包含可枚举的字符串键属性;
Object.keys
仅包含可枚举的自有字符串键属性;
Object.getOwnPropertyNames
包括自有的字符串键属性,即使是不可枚举的;
Object.getOwnPropertySymbols
仅对Symbol
键属性执行相同的操作,等等。)
3.4安全性
Map
可以安全地与用户提供的键值一起使用。- 在
Object
上设置用户提供的键值对可能会允许攻击者覆盖对象的原型,这可能会导致对象注入攻击。就像意外的键问题一样,这也可以通过使用null
原型对象来缓解。
3.5大小
Map
中的项目数量很容易从其size
属性中获得。- 确定
Objec
t 中的项目数量通常更麻烦,效率也较低。一种常见的方法是通过获取Object.keys()
返回的数组的长度。
3.6迭代
Map
是可迭代对象,所以它可以直接迭代。Object
没有实现迭代协议,因此对象默认情况下不能直接通过 JavaScript 的for...of
语句进行迭代。
一个对象可以实现迭代协议,或者你可以使用
Object.keys
或Object.entries
来获取一个对象的可迭代对象。
for...in
语句允许迭代对象的可枚举属性。
3.7性能
Map
在涉及频繁添加和删除键值对的场景中表现更好。Object
未针对频繁添加和删除键值对进行优化。
3.8序列化和解析
Map
没有对序列化或解析的原生支持。
(可以通过使用JSON.stringify()
及其replacer
参数和JSON.parse()
及其reviver
参数来为Map
构建自己的序列化和解析支持。参见 Stack Overflow 问题 How do you JSON.stringify an ES6 Map?)。Object
原生支持使用JSON.stringify()
序列化Object
到JSON
以及使用JSON.parse()
解析JSON
为Object
。
4、设置对象属性
4.1正确
正确的存储数据到 Map
中的方式是使用 set(key, value)
方法。
const contacts = new Map();
contacts.set("Jessie", { phone: "213-555-1234", address: "123 N 1st Ave" });
console.log('contacts.has("Jessie")=>'+contacts.has("Jessie")); // true
console.log('contacts.get("Hilary")=>'+contacts.get("Hilary")); // undefined
contacts.set("Hilary", { phone: "617-555-4321", address: "321 S 2nd St" });
console.log('contacts.get("Jessie")=>'+contacts.get("Jessie")); // {phone: "213-555-1234", address: "123 N 1st Ave"}
console.log('contacts.delete("Raymond")=>'+contacts.delete("Raymond")); // false
console.log('contacts.delete("Jessie")=>'+contacts.delete("Jessie")); // true
console.log(contacts.size); // 1
4.2能运行但不推荐
const wrongMap = new Map();
wrongMap["bla"] = "blaa";
wrongMap["bla2"] = "blaaa2";
console.log(wrongMap); // Map { bla: 'blaa', bla2: 'blaaa2' }
但这种设置属性的方式不会改变 Map
的数据结构。它使用的是通用对象的特性。‘bla’ 的值未被存储在Map
中,无法被查询到。其他的对这一数据的操作也会失败:
wrongMap.has("bla"); // false
wrongMap.delete("bla"); // false
console.log(wrongMap); // Map { bla: 'blaa', bla2: 'blaaa2' }
二、构造函数
Map()
构造函数创建 Map 对象。
Map()
只能用new
构造。尝试不使用new
调用它会抛出TypeError
。
1、语法
new Map()
new Map(iterable)
2、参数
iterable
可选,一个元素是键值对的数组或其他可迭代对象。(例如,包含两个元素的数组,如[[ 1, 'one' ],[ 2, 'two' ]]
。)每个键值对都被添加到新的 Map 中。
const myMap = new Map([
[1, "one"],
[2, "two"],
[3, "three"],
]);
三、静态属性及方法
1、属性
Map[@@species]
- 用于创建派生对象的构造函数。
Map[@@species]
静态访问器属性是一个未使用的访问器属性,指定了如何复制 Map 对象。
备注: 目前所有 Map 方法均未使用此属性。
1.1 语法
Map[Symbol.species]
1.2 返回值
- 调用
get @@species
的构造函数的值(this
)。返回值用于构造复制的Map
实例。
2、方法
2.1 groupBy()
Map.groupBy()
静态方法使用提供的回调函数返回的值对给定可迭代对象中的元素进行分组。最终返回的 Map 使用测试函数返回的唯一值作为键,可用于获取每个组中的元素组成的数组。- 该方法主要用于对与对象相关的元素进行分组,特别是当该对象可能随时间而变化时。如果对象不变,可以使用字符串表示它,并使用
Object.groupBy()
分组元素。
备注: 在某些浏览器的某些版本中,此方法被实现为
Array.prototype.groupToMap()
方法。
由于 web 兼容性问题,它现在以静态方法实现。
2.1.1 语法
groupBy(items, callbackFn)
2.1.2 参数
-
items
:一个将进行元素分组的可迭代对象(例如Array
)。 -
callbackFn
:对可迭代对象中的每个元素执行的函数。它应该返回一个值(对象或原始类型)来表示当前元素的分组。该函数被调用时将传入以下参数:
element
:数组中当前正在处理的元素。index
:正在处理的元素在数组中的索引。
2.1.3 返回值
- 一个包含了每一个组的键的
Map
对象,每个键都分配了一个包含关联组元素的数组。
2.1.4 描述
Map.groupBy()
为可迭代对象中的每个元素调用一次提供的callbackFn
函数。
该回调函数应返回一个值,用以表示相关元素的分组。
callbackFn
返回的值会被用作Map.groupBy()
返回的 Map 的键。
每个键都有一个相关联的数组,其中包含回调函数返回相同值的所有元素。
返回的
Map
中的元素和原始可迭代对象中的元素相同 (不是深拷贝)。
更改元素的内部结构将反映在原始可迭代对象和返回的 Map 中。
当你需要分组与特定对象相关的信息时,此方法非常有用。
因为即使对象被修改,它仍将作为返回的 Map 的键继续工作。
如果你改为为对象创建字符串表示形式,并在Object.groupBy()
中将其用作分组键,则必须在对象改变时维护原始对象和其表示之间的映射。
备注: 要访问返回的 Map
中的分组,必须使用最初用作 Map
键的相同对象(尽管可以修改其属性)。不能使用另一个恰好具有相同名称和属性的对象。
2.1.5 示例
- 使用 Map.groupBy()
首先,定义一个包含代表各种食品库存的对象的数组。每种食品都有一个 type
和一个 quantity
属性。
const inventory = [
{ name: "芦笋", type: "蔬菜", quantity: 9 },
{ name: "香蕉", type: "水果", quantity: 5 },
{ name: "山羊", type: "肉", quantity: 23 },
{ name: "樱桃", type: "水果", quantity: 12 },
{ name: "鱼", type: "肉", quantity: 22 },
];
下面的代码使用箭头函数(函数返回名为 restock
或 sufficient
的对象键,具体取决于元素是否满足 quantity < 6
)来调用 Map.groupBy()
。返回的 result
对象是一个 Map,因此我们需要使用键调用 get()
来获取数组。
const restock = { restock: true };
const sufficient = { restock: false };
const result = Map.groupBy(inventory, ({ quantity }) =>
quantity < 6 ? restock : sufficient,
);
console.log(result)
console.log(result.get(restock));
// [{ name: "香蕉", type: "水果", quantity: 5 }]
请注意,函数参数{ quantity }
是函数参数的对象解构语法的一个基本示例。
这会解构作为参数传递的对象的 quantity
属性,并将其赋值给函数体中名为 quantity
的变量。这是一种非常简洁的访问函数中相关元素的值的方式。
Map
的键在修改后仍可以使用。但是,不能重新创建键并仍然使用它。
因此,任何需要使用映射的内容都保留对其键的引用是非常重要的。
// 键被修改后仍可以使用
restock["fast"] = true;
console.log(result.get(restock));
// [{ name: "香蕉", type: "水果", quantity: 5 }]
// 不能使用新的键,即使它具有相同的结构!
const restock2 = { restock: true };
console.log('result.get(restock2):'+result.get(restock2)); // undefined
四、实例属性及方法
1、属性
1.1 Map.prototype.constructor
- 创建实例对象的构造函数。对于
Map
实例,初始值为Map
构造函数。
1.2 Map.prototype.size
- 返回
Map
对象中的键值对数量。
1.3 Map.prototype[@@toStringTag]
- @@toStringTag 属性的初始值是字符串 “Map”。该属性在
Object.prototype.toString()
中使用。
2、方法
2.1 set()
- 设置对象属性
2.1.1 语法
set(key, value)
2.1.2 参数
key
:要添加到 Map 对象的元素的键。value
:要添加到 Map 对象的元素的值。key/value
可以是任何 JavaScript 类型(任何原始值或任何类型的 JavaScript 对象)。
2.1.3 返回值是Map对象
const myMap = new Map();
// 将一个新元素添加到 Map 对象
myMap.set("bar", "foo");
myMap.set(1, "foobar");
console.log(myMap,'将一个新元素添加到 Map 对象')
// 在 Map 对象中更新某个元素的值
myMap.set("bar", "baz");
console.log(myMap,'在 Map 对象中更新某个元素的值')
2.1.4 链式使用
- 因为
set()
方法返回 Map 对象本身,所以你可以像下面这样链式调用它:
const myMap = new Map();
// 链式添加元素
myMap.set("bar", "foo").set(1, "foobar").set(2, "baz");
2.2 get()
- Map 实例的
get()
方法返回该 map 中的指定元素。如果与所提供的键相关联的值是一个对象,那么你将获得该对象的引用,对该对象所做的任何更改都会有效地在 Map 对象中修改它。
2.2.1 语法
get(key)
2.2.2 参数
key
:从 Map 对象返回的元素的键。
2.2.3 返回值
- 与指定键相关联的元素,如果键在 Map 对象中找不到,则返回
undefined
。
示例1:使用get()
const myMap = new Map();
myMap.set("bar", "foo");
console.log(myMap.get("bar")); // 返回 "foo"
console.log(myMap.get("baz")); // 返回 undefined
示例2:使用 get()
获取对对象的引用
const arr = [];
const myMap = new Map();
myMap.set("bar", arr);
myMap.get("bar").push("foo");
console.log(arr); // ["foo"]
console.log(myMap.get("bar")); // ["foo"]
注意,持有原始对象引用的映射实际上意味着对象不能被垃圾回收,这可能会导致意外的内存问题。如果希望存储在 map 中的对象具有与原始对象相同的生命周期,请考虑使用
WeakMap
。
WeakMap文档
2.3 delete()
- 从该 Map 中删除指定键的元素。
2.3.1 语法
delete(key)
2.3.2 参数
key
:要从 Map 对象中删除的元素的键。
2.3.3 返回值
- 如果
Map
对象中的元素存在并已被移除,则为true
; - 如果该元素不存在,则为
false
。
const myMap = new Map();
myMap.set("bar", "foo");
console.log(myMap.delete("bar")); // 返回 true。成功地移除元素
console.log(myMap.has("bar")); // 返回 false。"bar" 元素将不再存在于 Map 实例中
2.4 clear()
- 移除 Map 对象中所有的键值对。
2.4.1 语法
clear()
const map1 = new Map();
map1.set('bar', 'baz');
map1.set(1, 'foo');
console.log(map1.size);// 2
map1.clear();
console.log(map1.size);// 0
2.5 has()
- Map 实例的
has()
方法返回一个布尔值,指示具有指定键的元素是否存在。
2.5.1 语法
has(key)
2.5.2 参数
key
:用于测试 Map 对象中是否存在的元素的键。
2.5.3 返回值
- 如果 Map 对象中存在具有指定键的元素,则返回
true
;否则返回false
。
const myMap = new Map();
myMap.set("bar", "foo");
console.log(myMap.has("bar")); // true
console.log(myMap.has("baz")); // false
2.5.4 与get()
区别
get()
返回的是一个与指定键相关联的元素, has()
返回的是布尔值
2.6 keys()
- Map 实例的
keys()
方法返回一个新的 map 迭代器 (en-US)对象,该对象包含了此 Map 中每个元素的键,按插入顺序排列。
2.6.1 语法
keys()
2.6.2 返回值
- 一个新的可迭代迭代器对象 (en-US)。
const myMap = new Map();
myMap.set("0", "foo");
myMap.set(1, "bar");
myMap.set({}, "baz");
const mapIter = myMap.keys();
console.log(mapIter)
console.log(mapIter.next().value); // "0"
console.log(mapIter.next().value); // 1
console.log(mapIter.next().value); // {}
2.7 values()
values()
方法返回一个新的 map 迭代器 (en-US)对象,该对象包含此 map 中每个元素的值,按插入顺序排列。
2.7.1 语法
values()
2.7.2 返回值
- 一个新的可迭代迭代器对象 (en-US)。
const myMap = new Map();
myMap.set("0", "foo");
myMap.set(1, "bar");
myMap.set({}, "baz");
const mapIter = myMap.values();
console.log(mapIter)
console.log(mapIter.next().value); // "foo"
console.log(mapIter.next().value); // "bar"
console.log(mapIter.next().value); // "baz"
2.8 entries()
- Map 实例的
entries()
方法返回一个新的 map 迭代器 (en-US)对象,该对象包含了此 map 中的 每个元素的 [key, value] 对,按插入顺序排列。
2.8.1 语法
entries()
2.8.2 返回值
- 一个新的可迭代迭代器对象 (en-US)。
const myMap = new Map();
myMap.set("0", "foo");
myMap.set(1, "bar");
myMap.set({}, "baz");
const mapIter = myMap.entries();
console.log(mapIter)
console.log(mapIter.next().value); // ["0", "foo"]
console.log(mapIter.next().value); // [1, "bar"]
console.log(mapIter.next().value); // [Object, "baz"]
2.9 forEach()
- Map 实例的
forEach()
方法按插入顺序对该 map 中的每个键/值对执行一次提供的函数。
2.9.1 语法
forEach(callbackFn)
forEach(callbackFn, thisArg)
2.9.2 参数
callbackFn
:为 map 中每个元素执行的函数。使用以下参数调用该函数:
value
:每个迭代的值。key
:每个迭代的键。map
:正在迭代的 map。
thisArg
:非必填,执行callbackFn
时用作this
的值。
2.9.3 描述
forEach
方法会对 map
中每个真实存在的键执行一次提供的 callback
。
- 不会为被删除的键执行函数
- 会为存在但值为 undefined 的值执行函数
callbackFn
接收三个参数:
- 当前的 value
- 当前的 key
- 正在被遍历的 Map 对象
如果向 forEach
提供了 thisArg
参数,那么每次 callback
被调用时,其都会被传入以用作 this
的值。
否则,undefined
将会被传入以用作 this
的值。
最终 callback
可观察到的 this
值将会根据确定函数所观察到 this
的常用规则来确定。
- 每个值只被访问一次,除非它在
forEach
结束前被删除并被重新添加。 - 对于被访问前就删除的值,
callback
不会为其调用。 - 在
forEach
结束前被新添加的值都将会被访问。
function logMapElements(value, key, map) {
console.log(`map.get('${key}') = ${value}`);
}
new Map([
["foo", 3],
["bar", {}],
["baz", undefined],
]).forEach(logMapElements);
// 打印:
// "map.get('foo') = 3"
// "map.get('bar') = [object Object]"
// "map.get('baz') = undefined"