.map() a Javascript ES6 Map?

182

你会如何做这件事呢?本能地,我想要做:

var myMap = new Map([["thing1", 1], ["thing2", 2], ["thing3", 3]]);

// wishful, ignorant thinking
var newMap = myMap.map((key, value) => value + 1); // Map { 'thing1' => 2, 'thing2' => 3, 'thing3' => 4 }

我从新迭代协议的文档中收获不多。

我知道wu.js,但我正在运行一个Babel项目,不想包含Traceur,因为它似乎目前依赖于Traceur

我也不太清楚如何提取fitzgen/wu.js是如何做到这一点的并应用到我的项目上。

希望能有一个清晰、简洁的解释来弥补我在这方面的缺失。谢谢!


ES6 Map 的文档,供参考。


你能使用 Array.from 吗? - Ry-
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - neezer
好的,你可以编写自己的 map 函数,用于迭代器,并使用 for of - Ry-
有点挑剔,但如果你真的要在 Map 上进行“映射”,那么最终你会得到一个 Map。否则,你只是先将 Map 转换为 Array,然后再在 Array 上进行映射,而不是在 Map 上使用 .map()。你可以很容易地通过使用 ISO:dimap(x=>[...x], x=> new Map(x)) 在 Map 上进行映射。 - Dtipson
@Ry 嗯,我们可能可以编写自己的编程语言,但为什么要这样做呢?这是一个非常简单的事情,在大多数编程语言中已经存在了几十年。 - ruX
@ruX:区别在于“编写自己的map”是一个实际的选项(这也是在Array.prototype.map出现之前人们为数组所做的),而“编写自己的编程语言”则不太实际。很抱歉,JavaScript没有从可迭代对象到可迭代对象的map。你想让我说什么? - Ry-
15个回答

128

所以.map本身只提供了一个你关心的值… 尽管如此,有几种方法可以解决这个问题:

// instantiation
const myMap = new Map([
  [ "A", 1 ],
  [ "B", 2 ]
]);

// what's built into Map for you
myMap.forEach( (val, key) => console.log(key, val) ); // "A 1", "B 2"

// what Array can do for you
Array.from( myMap ).map(([key, value]) => ({ key, value })); // [{key:"A", value: 1}, ... ]

// less awesome iteration
let entries = myMap.entries( );
for (let entry of entries) {
  console.log(entry);
}

注意,在第二个例子中我使用了很多新东西... ...Array.from接收任何可迭代对象(任何您将使用[].slice.call( )的时候,再加上Sets和Maps),并将其转换为数组... ...当强制将Maps转换为数组时,会变成一个由数组组成的数组,其中el[0] === key && el[1] === value; (基本上和我上面预填充示例Map的格式相同)。

我在lambda的参数位置使用了数组解构,将这些数组位置分配给值,然后为每个元素返回一个对象。

如果你在生产环境中使用Babel,你需要使用Babel的浏览器polyfill(包括“core-js”和Facebook的“regenerator”)。
我非常确定它包含Array.from


是的,我注意到我需要使用Array.from来完成这个任务。顺便说一下,你的第一个例子并没有返回一个数组。 - neezer
@neezer 不,它不会。.forEach 无论何时都会直接返回 undefined,因为使用 forEach 的预期用途是基于副作用的简单迭代(自 ES5 以来一直如此)。要使用该 forEach,您需要手动构建一个数组并通过该 forEach.entries().keys().values() 返回的迭代器来填充它。 Array.from 并没有做任何非常独特的事情,它只是隐藏了进行遍历的特定丑陋方式。是的,通过包含 babel-core/browser.js(或类似)来获取 .from... - Norguard
7
Array.from接受一个映射函数作为参数,你不需要创建一个临时数组来进行映射并丢弃它。 - loganfsmyth
1
@dtc 是的,当你写一个对象时,你会写 const obj = { x: 1 };,当你写一段代码块时,你会写 if (...) { /* block */ },当你写一个箭头函数时,你会写 () => { return 1; },那么它如何知道什么是函数体,而不是对象的大括号呢?答案就是圆括号。否则,它会认为名称是代码块的标签,这是一个很少用到的特性,但你可以给 switch 或循环命名标签,这样你就可以通过名称 break; 退出它们。因此,如果缺少圆括号,你会得到一个带有标签的函数体,并且基于 MDN 的示例,包含语句 1 - Norguard
.forEach() 不是 .map()。为什么 JavaScript 的开发者总是会忽略这样基本的 API 呢?接下来你会告诉我你不能对 Iterator 进行 .map() 操作! - Timmmm
显示剩余2条评论

86

只需使用 Array.from(iterable, [mapFn])

var myMap = new Map([["thing1", 1], ["thing2", 2], ["thing3", 3]]);

var newEntries = Array.from(myMap, ([key, value]) => [key, value + 1]);
var newMap = new Map(newEntries);

2
谢谢你的回答,我希望它能够更高一些。我经常使用将Map扩展到临时数组的模式,毫不知情Array.from()还可以接受一个可选的映射函数作为第二个参数。 - Andru
这太棒了。而且在使用Map.values()时与Flow兼容,不像Array.values()仍然不兼容:facepalm:。 - ericsoco
我认为这个答案必须被提问者选中。因为这是唯一和正确的方法。 - Bender
1
在 TypeScript Playground 中运行此代码时,需要显式指定 newEntries 变量的类型,否则它会推断出错误的类型。var newEntries: Array<[string, number]> = Array.from(myMap, ([key, value]) => [key, value + 1]); - Omar Rodriguez
1
@OmarRodriguez,使用typescript@4.6.2似乎不再必要了。 - wegry
@wegry 在 TypeScript 4.8.3 上,即使没有显式设置 newEntries 的类型,我仍然存在一个类型问题。 - Sébastien BATEZAT

42

你应该只使用展开运算符:

var myMap = new Map([["thing1", 1], ["thing2", 2], ["thing3", 3]]);

var newArr = [...myMap].map(value => value[1] + 1);
console.log(newArr); //[2, 3, 4]

var newArr2 = [for(value of myMap) value = value[1] + 1];
console.log(newArr2); //[2, 3, 4]


看起来仍然需要使用 Array.from,供您参考。 - neezer
1
@neezer 你绝对、肯定必须使用 Babel 的 polyfill,在生产环境中,如果想要在页面上使用经过 Babel 转译的代码。除非你自己手动实现所有这些方法(包括 Facebook 的 Regenerator),否则没有其他办法。 - Norguard
8
注意,[...myMap].map(mapFn) 会创建一个临时数组然后丢弃它。Array.from 接受一个 mapFn 作为其第二个参数,请使用它。 - loganfsmyth
尝试一下:@neezer [...myMap].map(value => value[1] + 1) - Walter Chapilliquen - wZVanG
3
为什么不使用 Array.from(myMap.values(), val => val + 1); 呢? - loganfsmyth
显示剩余2条评论

7
您可以使用这个函数:
function mapMap(map, fn) {
  return new Map(Array.from(map, ([key, value]) => [key, fn(value, key, map)]));
}

使用方法:

var map1 = new Map([["A", 2], ["B", 3], ["C", 4]]);

var map2 = mapMap(map1, v => v * v);

console.log(map1, map2);
/*
Map { A → 2, B → 3, C → 4 }
Map { A → 4, B → 9, C → 16 }
*/

5

在 TypeScript 中,如果有人需要:

export {}

declare global {
    interface Map<K, V> {
        map<T>(predicate: (key: K, value: V) => T): Map<V, T>
    }
}

Map.prototype.map = function<K, V, T>(predicate: (value: V, key: K) => T): Map<K, T> {
    let map: Map<K, T> = new Map()

    this.forEach((value: V, key: K) => {
        map.set(key, predicate(value, key))
    })
    return map
}

这个方法可行,也是我首选的方法,但现在Map上的其他Map函数(例如get())都不可用了。你有什么想法吗?我在这篇帖子中描述了我的问题:https://dev59.com/RM36oIgBc1ULPQZF_q-K - serlingpa
1
算了,其实是我的客户端代码出了问题,导致各种方法返回 undefined,比如 .set()。现在已经修复好了。 - serlingpa

3

使用Array.from,我编写了一个TypeScript函数来映射值:

function mapKeys<T, V, U>(m: Map<T, V>, fn: (this: void, v: V) => U): Map<T, U> {
  function transformPair([k, v]: [T, V]): [T, U] {
    return [k, fn(v)]
  }
  return new Map(Array.from(m.entries(), transformPair));
}

const m = new Map([[1, 2], [3, 4]]);
console.log(mapKeys(m, i => i + 1));
// Map { 1 => 3, 3 => 5 }

3

实际上,在使用 Array.from 转换为数组后,您仍然可以拥有原始键的 Map。这是通过返回一个 数组 实现的,其中第一项是 key,第二项是转换后的 value

const originalMap = new Map([
  ["thing1", 1], ["thing2", 2], ["thing3", 3]
]);

const arrayMap = Array.from(originalMap, ([key, value]) => {
    return [key, value + 1]; // return an array
});

const alteredMap = new Map(arrayMap);

console.log(originalMap); // Map { 'thing1' => 1, 'thing2' => 2, 'thing3' => 3 }
console.log(alteredMap);  // Map { 'thing1' => 2, 'thing2' => 3, 'thing3' => 4 }

如果你不把那个返回为第一个数组项,你就会失去你的Map键。


2
您可以使用map()函数操作数组,但是对于Map对象并没有这样的操作。以下是来自Axel Rauschmayer博士的解决方案:
  • 将Map对象转换为包含[key,value]对的数组。
  • 对该数组进行map或filter操作。
  • 将结果再次转换回Map对象。
例如:
let map0 = new Map([
  [1, "a"],
  [2, "b"],
  [3, "c"]
]);

const map1 = new Map(
  [...map0]
  .map(([k, v]) => [k * 2, '_' + v])
);

导致了
{2 => '_a', 4 => '_b', 6 => '_c'}

1

我更喜欢扩展地图

export class UtilMap extends Map {  
  constructor(...args) { super(args); }  
  public map(supplier) {
      const mapped = new UtilMap();
      this.forEach(((value, key) => mapped.set(key, supplier(value, key)) ));
      return mapped;
  };
}

1
您可以使用myMap.forEach,在每次循环中,使用map.set来更改值。

myMap = new Map([
  ["a", 1],
  ["b", 2],
  ["c", 3]
]);

for (var [key, value] of myMap.entries()) {
  console.log(key + ' = ' + value);
}


myMap.forEach((value, key, map) => {
  map.set(key, value+1)
})

for (var [key, value] of myMap.entries()) {
  console.log(key + ' = ' + value);
}


我认为这个观点是错的。Array.map不会改变任何东西。 - Aaron

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接