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();
assert.equal(emptyMap.size, 0);其次,您可以将键值“对”(包含两个元素的数组)的可迭代对象(例如,数组)传递给构造函数
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);
assert.deepEqual(original, copy);.set() 和 .get() 用于写入和读取值(给定键)。
const map = new Map();
map.set('foo', 123);
assert.equal(map.get('foo'), 123);
// Unknown key:
assert.equal(map.get('bar'), undefined);
// Use the default value '' if an entry is missing:
assert.equal(map.get('bar') ?? '', '');.has() 检查映射是否具有给定键的条目。 .delete() 删除条目。
const map = new Map([['foo', 123]]);
assert.equal(map.has('foo'), true);
assert.equal(map.delete('foo'), true)
assert.equal(map.has('foo'), false).size 包含映射中的条目数。 .clear() 删除映射的所有条目。
const map = new Map()
.set('foo', true)
.set('bar', false)
;
assert.equal(map.size, 2)
map.clear();
assert.equal(map.size, 0).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() 返回的可迭代对象转换为数组
assert.deepEqual(
Array.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() 返回的可迭代对象转换为数组
assert.deepEqual(
Array.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],
]);
assert.deepEqual(
Array.from(map1.keys()), ['a', 'b']);
const map2 = new Map([
['b', 2],
['a', 1],
]);
assert.deepEqual(
Array.from(map2.keys()), ['b', 'a']);只要映射仅使用字符串和符号作为键,您就可以将其转换为对象(通过 Object.fromEntries())
const map = new Map([
['a', 1],
['b', 2],
]);
const obj = Object.fromEntries(map);
assert.deepEqual(
obj, {a: 1, b: 2});您还可以将具有字符串或符号键的对象转换为映射(通过 Object.entries())
const obj = {
a: 1,
b: 2,
};
const map = new Map(Object.entries(obj));
assert.deepEqual(
map, new Map([['a', 1], ['b', 2]]));countChars() 返回一个映射,该映射将字符映射到出现次数。
function countChars(chars) {
const charCounts = new Map();
for (let ch of chars) {
ch = ch.toLowerCase();
const prevCount = charCounts.get(ch) ?? 0;
charCounts.set(ch, prevCount+1);
}
return charCounts;
}
const result = countChars('AaBccc');
assert.deepEqual(
Array.from(result),
[
['a', 2],
['b', 1],
['c', 3],
]
);任何值都可以是键,甚至是对象
const map = new Map();
const KEY1 = {};
const KEY2 = {};
map.set(KEY1, 'hello');
map.set(KEY2, 'world');
assert.equal(map.get(KEY1), 'hello');
assert.equal(map.get(KEY2), 'world');大多数映射操作都需要检查值是否等于其中一个键。它们通过内部操作 SameValueZero 来实现,该操作的工作方式与 === 类似,但将 NaN 视为与其自身相等。
因此,您可以在映射中使用 NaN 作为键,就像任何其他值一样
> const map = new Map();
> map.set(NaN, 123);
> map.get(NaN)
123不同的对象始终被认为是不同的。这是无法更改的(目前还不能 - 配置键相等性在 TC39 的长期路线图上)。
> new Map().set({}, 1).set({}, 2).size
2您可以对数组进行 .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
);
assert.deepEqual(
Array.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
);
assert.deepEqual(Array.from(filteredMap),
[[1,'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)
assert.deepEqual(
Array.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']]);
assert.equal(map.get(1), 'one');
assert.equal(map.get(5), undefined);.set(key: K, value: V): this [ES6]
将给定键映射到给定值。如果已经存在一个键为 key 的条目,则更新该条目。否则,将创建一个新条目。此方法返回 this,这意味着您可以链接它。
const map = new Map([[1, 'one'], [2, 'two']]);
map.set(1, 'ONE!')
.set(3, 'THREE!');
assert.deepEqual(
Array.from(map.entries()),
[[1, 'ONE!'], [2, 'two'], [3, 'THREE!']]);.has(key: K): boolean [ES6]
返回给定键是否存在于此映射中。
const map = new Map([[1, 'one'], [2, 'two']]);
assert.equal(map.has(1), true); // key exists
assert.equal(map.has(5), false); // key does not exist.delete(key: K): boolean [ES6]
如果存在一个键为 key 的条目,则删除该条目并返回 true。否则,不执行任何操作并返回 false。
const map = new Map([[1, 'one'], [2, 'two']]);
assert.equal(map.delete(1), true);
assert.equal(map.delete(5), false); // nothing happens
assert.deepEqual(
Array.from(map.entries()),
[[2, 'two']]);Map<K,V>.prototype:处理所有条目get .size: number [ES6]
返回此映射具有的条目数。
const map = new Map([[1, 'one'], [2, 'two']]);
assert.equal(map.size, 2);.clear(): void [ES6]
从此映射中删除所有条目。
const map = new Map([[1, 'one'], [2, 'two']]);
assert.equal(map.size, 2);
map.clear();
assert.equal(map.size, 0);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']]);
map.forEach((value, key) => console.log(value, key));
// 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;
map.get(theKey);您通常希望通过值比较映射键(如果两个键具有相同的内容,则认为它们相等)。这排除了对象。但是,有一种情况下可以使用对象作为键:将数据从外部附加到对象。但是,弱映射可以更好地满足这种用例,在弱映射中,条目不会阻止对键进行垃圾回收(有关详细信息,请参阅下一章)。
原则上,映射是无序的。对条目进行排序的主要原因是为了使列出条目、键或值的操作具有确定性。例如,这有助于测试。
.size 表示大小,而数组使用 .length?在 JavaScript 中,可索引序列(例如数组和字符串)具有 .length 属性,而未索引集合(例如映射和集合)具有 .size 属性
.length 基于索引;它始终是最高索引加 1。.size 统计集合中的元素数量。 测验
参见 测验应用程序。