.map () карту Javascript ES6?


89

Как бы вы это сделали? Инстинктивно я хочу сделать:

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 , от которого, похоже, он сейчас зависит .

Я также немного не понимаю, как извлечь, как fitzgen / wu.js сделал это в мой собственный проект.

Хотелось бы четкого и краткого объяснения того, что мне здесь не хватает. Благодарность!


Документы для карты ES6 , к сведению


Вы умеете пользоваться Array.from?
Ry-

@minitech Возможно, с полифиллом ... разве без него нельзя?
neezer

Что ж, вы можете написать свою собственную mapфункцию для использования с итерациями, используя for of.
Ry-

Супер придирка, но если бы вы действительно собирались наносить карту поверх карты, вы бы вернули карту в конце. В противном случае вы просто сначала преобразуете карту в массив и отображаете массив, а не .map () в карту. Вы можете легко отобразить карту с помощью ISO: dimap (x => [... x], x => new Map (x));
Dtipson

@Ry ну, наверное, мы можем написать свой собственный язык программирования, но почему ..? Это довольно простая вещь и существует в большинстве языков программирования уже много десятилетий.
ruX

Ответы:


73

Таким образом, .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( ), плюс наборы и карты) и превращает ее в массив ... ... Карты , при принудительном преобразовании в массив превращается в массив массивов, where el[0] === key && el[1] === value;(в основном, в том же формате, который я предварительно заполнил в моем примере Map выше).

Я использую деструктуризацию массива в позиции аргумента лямбда-выражения, чтобы присвоить этим точкам массива значения, прежде чем возвращать объект для каждого el.

Если вы используете Babel в производственной среде, вам нужно будет использовать полифилл браузера Babel (который включает «core-js» и «регенератор» Facebook).
Я совершенно уверен, что он содержит Array.from.


Да, я просто заметил, что, похоже, мне понадобится Array.fromдля этого. Ваш первый пример не возвращает массив, кстати.
neezer

@neezer нет, это не так. все время полностью .forEachвозвращается undefined, поскольку ожидается использование forEachпростой итерации, основанной на побочных эффектах (так же, как это было со времен ES5). Чтобы использовать это forEach, вам нужно создать массив и заполнить его вручную, либо с помощью указанного, forEachлибо с помощью итератора, возвращаемого .entries()или .keys( )или .values( ). Array.fromне делает ничего невероятно уникального, а просто скрывает это конкретное уродство выполнения указанного обхода. И да, проверьте это, включив babel-core/browser.js(или что-то еще), вы получите .from...
Norguard

4
Смотрите мой ответ, Array.fromпринимает функцию карты в качестве параметра, вам не нужно создавать временный массив, чтобы отобразить его и отбросить.
loganfsmyth,

Можете ли вы объяснить, почему объект должен заключаться в круглые скобки? Я все еще новичок в ES6 и не могу понять эту часть. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… говорит, что это интерпретируется как ярлык, и я не уверен, что это означает
dtc

1
@dtc Да, когда вы пишете объект, который вы пишете const obj = { x: 1 };, когда вы пишете блок кода, вы пишете if (...) { /* block */ }, когда вы пишете функцию стрелки, которую вы пишете () => { return 1; }, так как она узнает, что это тело функции, а не фигурные скобки объекта? И ответ - круглые скобки. В противном случае он ожидает, что имя будет меткой для блока кода, который редко используется, но вы можете пометить switchцикл или цикл, чтобы вы могли break;выйти из них по имени. Так что, если он отсутствует, у вас есть тело функции с меткой и оператором 1, основанным на примере MDN
Norguard

34

Вам просто нужно использовать оператор Spread :

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, чтобы использовать код, транслируемый Babel, на своей странице в продакшене. Нет никакого способа обойти это, если вы не реализуете все эти методы (включая Regenerator Facebook) самостоятельно, вручную ...
Norguard

@Norguard Понятно. Я имел в виду только изменение конфигурации. На самом деле, скорее в сторону. :)
neezer

5
Просто обратите внимание, [...myMap].map(mapFn)собирается создать временный массив, а затем отбросить его. Array.fromпринимает в mapFnкачестве второго аргумента, просто используйте его.
loganfsmyth

2
@wZVanG Почему бы и нет Array.from(myMap.values(), val => val + 1);?
loganfsmyth

21

Просто используйте Array.from(iterable, [mapFn]).

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

var newArr = Array.from(myMap.values(), value => value + 1);

Спасибо за этот ответ, я бы хотел, чтобы он был выше. Я все время использую шаблон распространения карты во временный массив, беспечно не подозревая, Array.from()что функция сопоставления была необязательным вторым параметром.
Андру

4

Вы можете использовать эту функцию:

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 }
*/

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

На самом деле вы все еще можете иметь Mapисходные ключи после преобразования в массив с помощью Array.from. Это возможно, если вернуть массив , где первый элемент - это 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ключи.


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 ничего не мутирует.
Аарон

0

const mapMap = (callback, map) => new Map(Array.from(map).map(callback))

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

var newMap = mapMap((pair) => [pair[0], pair[1] + 1], myMap); // Map { 'thing1' => 2, 'thing2' => 3, 'thing3' => 4 }


0

Если вы не хотите заранее преобразовывать всю карту в массив и / или деструктурировать массивы ключ-значение, вы можете использовать эту глупую функцию:

/**
 * Map over an ES6 Map.
 *
 * @param {Map} map
 * @param {Function} cb Callback. Receives two arguments: key, value.
 * @returns {Array}
 */
function mapMap(map, cb) {
  let out = new Array(map.size);
  let i = 0;
  map.forEach((val, key) => {
    out[i++] = cb(key, val);
  });
  return out;
}

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

console.log(
  mapMap(map, (k, v) => `${k}-${v}`).join(', ')
); // a-1, b-2, c-3


0
Map.prototype.map = function(callback) {
  const output = new Map()
  this.forEach((element, key)=>{
    output.set(key, callback(element, key))
  })
  return output
}

const myMap = new Map([["thing1", 1], ["thing2", 2], ["thing3", 3]])
// no longer wishful thinking
const newMap = myMap.map((value, key) => value + 1)
console.info(myMap, newMap)

Зависит от вашего религиозного рвения в том, чтобы избегать редактирования прототипов, но я считаю, что это позволяет мне сохранять интуитивно понятный вид.


0

Вы можете использовать массивы map (), но для Maps такой операции нет. Решение от доктора Акселя Раушмайера :

  • Преобразуйте карту в массив пар [ключ, значение].
  • Сопоставьте или отфильтруйте массив.
  • Преобразуйте результат обратно в карту.

Пример:

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'}

0

Может так:

const m = new Map([["a", 1], ["b", 2], ["c", 3]]);
m.map((k, v) => [k, v * 2]); // Map { 'a' => 2, 'b' => 4, 'c' => 6 }

Раньше вам нужно было только патч обезьяны Map:

Map.prototype.map = function(func){
    return new Map(Array.from(this, ([k, v]) => func(k, v)));
}

Мы могли бы написать более простую форму этого патча:

Map.prototype.map = function(func){
    return new Map(Array.from(this, func));
}

Но мы бы тогда заставили нас написать, m.map(([k, v]) => [k, v * 2]);что мне кажется более болезненным и уродливым.

Только значения сопоставления

Мы также можем отображать только значения, но я бы не советовал использовать это решение, поскольку оно слишком конкретное. Тем не менее это можно сделать, и у нас будет следующий API:

const m = new Map([["a", 1], ["b", 2], ["c", 3]]);
m.map(v => v * 2); // Map { 'a' => 2, 'b' => 4, 'c' => 6 }

Как и перед патчем таким образом:

Map.prototype.map = function(func){
    return new Map(Array.from(this, ([k, v]) => [k, func(v)]));
}

Может быть, вы можете иметь и то, и другое, назвав вторую, mapValuesчтобы было ясно, что вы на самом деле не отображаете объект, как это, вероятно, ожидалось.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.