Map
)Map<K,V>
Map<K,V>.prototype
:处理单个条目Map<K,V>.prototype
:处理所有条目Map<K,V>.prototype
:迭代和循环.size
表示大小,而数组使用 .length
?在 ES6 之前,JavaScript 没有用于字典的数据结构,并且(滥用)对象作为从字符串到任意值的字典。ES6 带来了映射,它是从任意值到任意值的字典。
Map
的实例将键映射到值。单个键值映射称为_条目_。
创建映射有三种常用方法。
首先,您可以使用不带任何参数的构造函数来创建一个空映射
const emptyMap = new Map();
.equal(emptyMap.size, 0); assert
其次,您可以将键值“对”(包含两个元素的数组)的可迭代对象(例如,数组)传递给构造函数
const map = new Map([
1, 'one'],
[2, 'two'],
[3, 'three'], // trailing comma is ignored
[; ])
第三,.set()
方法将条目添加到映射中,并且可以链接
const map = new Map()
.set(1, 'one')
.set(2, 'two')
.set(3, 'three');
正如我们稍后将看到的,映射也是键值对的可迭代对象。因此,您可以使用构造函数来创建映射的副本。该副本是_浅_副本:键和值相同;它们不会被复制。
const original = new Map()
.set(false, 'no')
.set(true, 'yes');
const copy = new Map(original);
.deepEqual(original, copy); assert
.set()
和 .get()
用于写入和读取值(给定键)。
const map = new Map();
.set('foo', 123);
map
.equal(map.get('foo'), 123);
assert// Unknown key:
.equal(map.get('bar'), undefined);
assert// Use the default value '' if an entry is missing:
.equal(map.get('bar') ?? '', ''); assert
.has()
检查映射是否具有给定键的条目。 .delete()
删除条目。
const map = new Map([['foo', 123]]);
.equal(map.has('foo'), true);
assert.equal(map.delete('foo'), true)
assert.equal(map.has('foo'), false) assert
.size
包含映射中的条目数。 .clear()
删除映射的所有条目。
const map = new Map()
.set('foo', true)
.set('bar', false)
;
.equal(map.size, 2)
assert.clear();
map.equal(map.size, 0) assert
.keys()
返回映射键的可迭代对象
const map = new Map()
.set(false, 'no')
.set(true, 'yes')
;
for (const key of map.keys()) {
console.log(key);
}// Output:
// false
// true
我们使用 Array.from()
将 .keys()
返回的可迭代对象转换为数组
.deepEqual(
assertArray.from(map.keys()),
false, true]); [
.values()
的工作方式与 .keys()
类似,但用于值而不是键。
.entries()
返回映射条目的可迭代对象
const map = new Map()
.set(false, 'no')
.set(true, 'yes')
;
for (const entry of map.entries()) {
console.log(entry);
}// Output:
// [false, 'no']
// [true, 'yes']
Array.from()
将 .entries()
返回的可迭代对象转换为数组
.deepEqual(
assertArray.from(map.entries()),
false, 'no'], [true, 'yes']]); [[
映射实例也是条目的可迭代对象。在以下代码中,我们使用 解构 来访问 map
的键和值
for (const [key, value] of map) {
console.log(key, value);
}// Output:
// false, 'no'
// true, 'yes'
映射记录创建条目的顺序,并在列出条目、键或值时遵循该顺序
const map1 = new Map([
'a', 1],
['b', 2],
[;
]).deepEqual(
assertArray.from(map1.keys()), ['a', 'b']);
const map2 = new Map([
'b', 2],
['a', 1],
[;
]).deepEqual(
assertArray.from(map2.keys()), ['b', 'a']);
只要映射仅使用字符串和符号作为键,您就可以将其转换为对象(通过 Object.fromEntries()
)
const map = new Map([
'a', 1],
['b', 2],
[;
])const obj = Object.fromEntries(map);
.deepEqual(
assert, {a: 1, b: 2}); obj
您还可以将具有字符串或符号键的对象转换为映射(通过 Object.entries()
)
const obj = {
a: 1,
b: 2,
;
}const map = new Map(Object.entries(obj));
.deepEqual(
assert, new Map([['a', 1], ['b', 2]])); map
countChars()
返回一个映射,该映射将字符映射到出现次数。
function countChars(chars) {
const charCounts = new Map();
for (let ch of chars) {
= ch.toLowerCase();
ch const prevCount = charCounts.get(ch) ?? 0;
.set(ch, prevCount+1);
charCounts
}return charCounts;
}
const result = countChars('AaBccc');
.deepEqual(
assertArray.from(result),
['a', 2],
['b', 1],
['c', 3],
[
]; )
任何值都可以是键,甚至是对象
const map = new Map();
const KEY1 = {};
const KEY2 = {};
.set(KEY1, 'hello');
map.set(KEY2, 'world');
map
.equal(map.get(KEY1), 'hello');
assert.equal(map.get(KEY2), 'world'); assert
大多数映射操作都需要检查值是否等于其中一个键。它们通过内部操作 SameValueZero 来实现,该操作的工作方式与 ===
类似,但将 NaN
视为与其自身相等。
因此,您可以在映射中使用 NaN
作为键,就像任何其他值一样
> const map = new Map();
> map.set(NaN, 123);
> map.get(NaN)123
不同的对象始终被认为是不同的。这是无法更改的(目前还不能 - 配置键相等性在 TC39 的长期路线图上)。
> new Map().set({}, 1).set({}, 2).size2
您可以对数组进行 .map()
和 .filter()
操作,但映射没有此类操作。解决方案是
我将使用以下映射来演示它是如何工作的。
const originalMap = new Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c');
映射 originalMap
const mappedMap = new Map( // step 3
Array.from(originalMap) // step 1
.map(([k, v]) => [k * 2, '_' + v]) // step 2
;
).deepEqual(
assertArray.from(mappedMap),
2,'_a'], [4,'_b'], [6,'_c']]); [[
过滤 originalMap
const filteredMap = new Map( // step 3
Array.from(originalMap) // step 1
.filter(([k, v]) => k < 3) // step 2
;
).deepEqual(Array.from(filteredMap),
assert1,'a'], [2,'b']]); [[
Array.from()
将任何可迭代对象转换为数组。
没有用于合并映射的方法,这就是为什么我们必须使用与上一节类似的解决方法。
让我们合并以下两个映射
const map1 = new Map()
.set(1, '1a')
.set(2, '1b')
.set(3, '1c')
;
const map2 = new Map()
.set(2, '2b')
.set(3, '2c')
.set(4, '2d')
;
为了合并 map1
和 map2
,我们创建一个新数组,并将 map1
和 map2
的条目(键值对)扩展(...
)到其中(通过迭代)。然后我们将数组转换回映射。所有这些都在 A 行中完成
const combinedMap = new Map([...map1, ...map2]); // (A)
.deepEqual(
assertArray.from(combinedMap), // convert to Array for comparison
1, '1a' ],
[ [ 2, '2b' ],
[ 3, '2c' ],
[ 4, '2d' ] ]
[ ; )
练习:合并两个映射
exercises/maps/combine_maps_test.mjs
Map<K,V>
注意:为了简洁起见,我假设所有键都具有相同的类型 K
,并且所有值都具有相同的类型 V
。
new Map<K, V>(entries?: Iterable<[K, V]>)
[ES6]
如果您不提供参数 entries
,则会创建一个空映射。如果您确实提供了 [键,值] 对的可迭代对象,则这些对将作为条目添加到映射中。例如
const map = new Map([
1, 'one' ],
[ 2, 'two' ],
[ 3, 'three' ], // trailing comma is ignored
[ ; ])
Map<K,V>.prototype
:处理单个条目.get(key: K): V
[ES6]
返回在此映射中 key
映射到的 value
。如果此映射中没有键 key
,则返回 undefined
。
const map = new Map([[1, 'one'], [2, 'two']]);
.equal(map.get(1), 'one');
assert.equal(map.get(5), undefined); assert
.set(key: K, value: V): this
[ES6]
将给定键映射到给定值。如果已经存在一个键为 key
的条目,则更新该条目。否则,将创建一个新条目。此方法返回 this
,这意味着您可以链接它。
const map = new Map([[1, 'one'], [2, 'two']]);
.set(1, 'ONE!')
map.set(3, 'THREE!');
.deepEqual(
assertArray.from(map.entries()),
1, 'ONE!'], [2, 'two'], [3, 'THREE!']]); [[
.has(key: K): boolean
[ES6]
返回给定键是否存在于此映射中。
const map = new Map([[1, 'one'], [2, 'two']]);
.equal(map.has(1), true); // key exists
assert.equal(map.has(5), false); // key does not exist assert
.delete(key: K): boolean
[ES6]
如果存在一个键为 key
的条目,则删除该条目并返回 true
。否则,不执行任何操作并返回 false
。
const map = new Map([[1, 'one'], [2, 'two']]);
.equal(map.delete(1), true);
assert.equal(map.delete(5), false); // nothing happens
assert.deepEqual(
assertArray.from(map.entries()),
2, 'two']]); [[
Map<K,V>.prototype
:处理所有条目get .size: number
[ES6]
返回此映射具有的条目数。
const map = new Map([[1, 'one'], [2, 'two']]);
.equal(map.size, 2); assert
.clear(): void
[ES6]
从此映射中删除所有条目。
const map = new Map([[1, 'one'], [2, 'two']]);
.equal(map.size, 2);
assert.clear();
map.equal(map.size, 0); assert
Map<K,V>.prototype
:迭代和循环迭代和循环都按将条目添加到映射中的顺序进行。
.entries(): Iterable<[K,V]>
[ES6]
返回一个可迭代对象,该对象包含此映射中每个条目的一个 [键,值] 对。这些对是长度为 2 的数组。
const map = new Map([[1, 'one'], [2, 'two']]);
for (const entry of map.entries()) {
console.log(entry);
}// Output:
// [1, 'one']
// [2, 'two']
.forEach(callback: (value: V, key: K, theMap: Map<K,V>) => void, thisArg?: any): void
[ES6]
第一个参数是一个回调函数,该函数针对此映射中的每个条目调用一次。如果提供了 thisArg
,则每次调用时 this
都设置为它。否则,this
设置为 undefined
。
const map = new Map([[1, 'one'], [2, 'two']]);
.forEach((value, key) => console.log(value, key));
map// Output:
// 'one', 1
// 'two', 2
.keys(): Iterable<K>
[ES6]
返回此映射中所有键的可迭代对象。
const map = new Map([[1, 'one'], [2, 'two']]);
for (const key of map.keys()) {
console.log(key);
}// Output:
// 1
// 2
.values(): Iterable<V>
[ES6]
返回此映射中所有值的可迭代对象。
const map = new Map([[1, 'one'], [2, 'two']]);
for (const value of map.values()) {
console.log(value);
}// Output:
// 'one'
// 'two'
[Symbol.iterator](): Iterable<[K,V]>
[ES6]
迭代映射的默认方式。与 .entries()
相同。
const map = new Map([[1, 'one'], [2, 'two']]);
for (const [key, value] of map) {
console.log(key, value);
}// Output:
// 1, 'one'
// 2, 'two'
如果您需要一个字典式的数据结构,其键既不是字符串也不是符号,那么您别无选择:您必须使用映射。
但是,如果您的键是字符串或符号,则必须决定是否使用对象。一个粗略的通用准则是
是否存在一组固定的键(在开发时已知)?
然后使用对象 obj
并通过固定键访问值
const value = obj.key;
键集是否可以在运行时更改?
然后使用映射 map
并通过存储在变量中的键访问值
const theKey = 123;
.get(theKey); map
您通常希望通过值比较映射键(如果两个键具有相同的内容,则认为它们相等)。这排除了对象。但是,有一种情况下可以使用对象作为键:将数据从外部附加到对象。但是,弱映射可以更好地满足这种用例,在弱映射中,条目不会阻止对键进行垃圾回收(有关详细信息,请参阅下一章)。
原则上,映射是无序的。对条目进行排序的主要原因是为了使列出条目、键或值的操作具有确定性。例如,这有助于测试。
.size
表示大小,而数组使用 .length
?在 JavaScript 中,可索引序列(例如数组和字符串)具有 .length
属性,而未索引集合(例如映射和集合)具有 .size
属性
.length
基于索引;它始终是最高索引加 1。.size
统计集合中的元素数量。测验
参见 测验应用程序。