функция карты для объектов (вместо массивов)


1068

У меня есть объект:

myObject = { 'a': 1, 'b': 2, 'c': 3 }

Я ищу нативный метод, похожий на Array.prototype.mapтот, который будет использоваться следующим образом:

newObject = myObject.map(function (value, label) {
    return value * value;
});

// newObject is now { 'a': 1, 'b': 4, 'c': 9 }

Есть ли в JavaScript такая mapфункция для объектов? (Я хочу это для Node.JS, поэтому меня не волнуют кросс-браузерные проблемы.)


1
Используется большинство ответов Object.keys, которые не имеют четко определенного порядка. Это может быть проблематично, я предлагаю использовать Object.getOwnPropertyNamesвместо этого.
Oriol

14
Удивительно для меня, что JS не предусмотрел эту невероятно рутинную задачу.
jchook

3
@ Ориоль, ты уверен в этом? Согласно веб-документам MDN, порядок элементов массива согласован между Object.keysи Object.getOwnPropertyNames. См developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
Bart

@Bart Порядок Object.keysзависит от реализации. См. Stackoverflow.com/a/30919039/1529630
Oriol

4
@Oriol Вы не должны полагаться на порядок ключей в объектах в любом случае, так что это как бы не имеет отношения к вопросу - поскольку он никогда не указывал, что порядок имеет значение для него. И если для него важен порядок, он вообще не должен использовать объект.
BT

Ответы:


1546

Там нет родного mapдля Objectобъекта, но как насчет этого:

var myObject = { 'a': 1, 'b': 2, 'c': 3 };

Object.keys(myObject).map(function(key, index) {
  myObject[key] *= 2;
});

console.log(myObject);
// => { 'a': 2, 'b': 4, 'c': 6 }

Но вы можете легко перебрать объект, используя for ... in:

var myObject = { 'a': 1, 'b': 2, 'c': 3 };

for (var key in myObject) {
  if (myObject.hasOwnProperty(key)) {
    myObject[key] *= 2;
  }
}

console.log(myObject);
// { 'a': 2, 'b': 4, 'c': 6 }

Обновить

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

var myObject = { 'a': 1, 'b': 2, 'c': 3 };

// returns a new object with the values at each key mapped using mapFn(value)
function objectMap(object, mapFn) {
  return Object.keys(object).reduce(function(result, key) {
    result[key] = mapFn(object[key])
    return result
  }, {})
}

var newObject = objectMap(myObject, function(value) {
  return value * 2
})

console.log(newObject);
// => { 'a': 2, 'b': 4, 'c': 6 }

console.log(myObject);
// => { 'a': 1, 'b': 2, 'c': 3 }

Array.prototype.reduceуменьшает массив до единственного значения путем некоторого слияния предыдущего значения с текущим. Цепочка инициализируется пустым объектом {}. На каждой итерации myObjectдобавляется новый ключ с двойным ключом в качестве значения.

Обновить

С новыми функциями ES6, есть более элегантный способ выразить objectMap.

const objectMap = (obj, fn) =>
  Object.fromEntries(
    Object.entries(obj).map(
      ([k, v], i) => [k, fn(v, k, i)]
    )
  )
  
const myObject = { a: 1, b: 2, c: 3 }

console.log(objectMap(myObject, v => 2 * v)) 


3
Да, я бы не рекомендовал свое решение для такого рода проблем. Вот почему я обновил свой ответ альтернативным, лучшим решением.
Amberlamps

14
@KhalilRavanna Я думаю, что вы неправильно прочитали код здесь - этот ответ не используется mapправильно, потому что он не делает return- он злоупотребляет, mapкак если бы это был forEachвызов. Если бы он действительно это сделал, return myObject[value] * 2 то результатом был бы массив, содержащий удвоенные исходные значения, вместо объекта, содержащего исходные ключи с удвоенными значениями, причем последний явно соответствует тому, что запрашивал OP.
Альнитак

3
@Amberlamps Ваш .reduceпример в порядке, но, по- моему , он по-прежнему далеко не удобен .mapдля объектов, поскольку .reduceобратный вызов не только должен соответствовать способу .reduceработы, но также требует, чтобы он myObjectбыл доступен в лексической области видимости. Последнее, в частности, делает невозможным просто передать ссылку на функцию в обратном вызове, требуя вместо этого анонимную функцию на месте.
Альнитак

9
TBH Я бы скорее проголосовал за ответ, который только (или изначально) предоставил второе решение, представленное в этом ответе. Первый работает и не обязательно ошибается, но, как утверждают другие, мне не нравится, когда карта используется таким образом. Когда я вижу карту, мой разум автоматически размышляет о «неизменной структуре данных»
mjohnsonengr

2
.mapэто неподходящий метод для использования, когда вы не собираетесь использовать результирующий отображенный массив - если вы хотите использовать только побочные эффекты, такие как в вашем первом коде, вы должны определенно использовать forEachвместо этого.
CertainPerformance

308

Как насчет одного вкладыша с немедленным назначением переменной в простом JS ( ES6 / ES2015 )?

Использование оператора распространения и синтаксиса имени вычисляемого ключа :

let newObj = Object.assign({}, ...Object.keys(obj).map(k => ({[k]: obj[k] * obj[k]})));

jsbin

Другая версия, использующая Reduce:

let newObj = Object.keys(obj).reduce((p, c) => ({...p, [c]: obj[c] * obj[c]}), {});

jsbin

Первый пример как функция:

const oMap = (o, f) => Object.assign({}, ...Object.keys(o).map(k => ({ [k]: f(o[k]) })));

// To square each value you can call it like this:
let mappedObj = oMap(myObj, (x) => x * x);

jsbin

Если вы хотите отобразить вложенный объект рекурсивно в функциональном стиле, это можно сделать так:

const sqrObjRecursive = obj =>
  Object.keys(obj).reduce(
    (newObj, key) =>
      obj[key] && typeof obj[key] === "object"
        ? { ...newObj, [key]: sqrObjRecursive(obj[key]) } // recurse.
        : { ...newObj, [key]: obj[key] * obj[key] }, // square val.
    {}
  );       

jsbin

Или более обязательно, как это:

const sqrObjRecursive = obj => {
  Object.keys(obj).forEach(key => {
    if (typeof obj[key] === "object") obj[key] = sqrObjRecursive(obj[key]);
    else obj[key] = obj[key] * obj[key];
  });
  return obj;
};

jsbin

Начиная с ES7 / ES2016 вы можете использовать Object.entries()вместо, Object.keys()например, так:

let newObj = Object.assign({}, ...Object.entries(obj).map(([k, v]) => ({[k]: v * v})));

ES2019 представил Object.fromEntries(), что упрощает это еще больше:

let newObj = Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, v * v]));


Унаследованные свойства и прототип цепочки:

В некоторых редких случаях вам может понадобиться отобразить объект класса, который содержит свойства унаследованного объекта в его цепочке прототипов . В таких случаях Object.keys()не будет работать, потому Object.keys()что не перечисляет унаследованные свойства. Если вам нужно отобразить унаследованные свойства, вы должны использовать for (key in myObj) {...}.

Вот пример объекта, который наследует свойства другого объекта и как Object.keys()не работает в таком сценарии.

const obj1 = { 'a': 1, 'b': 2, 'c': 3}
const obj2 = Object.create(obj1);  // One of multiple ways to inherit an object in JS.

// Here you see how the properties of obj1 sit on the 'prototype' of obj2
console.log(obj2)  // Prints: obj2.__proto__ = { 'a': 1, 'b': 2, 'c': 3}

console.log(Object.keys(obj2));  // Prints: an empty Array.

for (key in obj2) {
  console.log(key);              // Prints: 'a', 'b', 'c'
}

jsbin

Однако, пожалуйста, сделайте мне одолжение и избегайте наследства . :-)


1
Красиво, но меня поймал тот факт, что Object.keysне перечисляются унаследованные свойства. Я предлагаю вам добавить предупреждение.
Дэвид Браун

Чтобы сократить ваш ответ, просто используйте Object.entries({a: 1, b: 2, c: 3}).

1
У вас есть некоторые опечатки (вы называете их (o, f)аргументами, но употребляете их objв теле.
kzahel

4
Да, а затем пусть следующий человек потратит несколько минут, чтобы выяснить, что делает эта функция? :)
Андрей Попов

1
Решение Object.assign(...Object.entries(obj).map(([k, v]) => ({[k]: v * v})))не работает, если objэто пустой объект. Изменить на: Object.assign({}, ...Object.entries(obj).map(([k, v]) => ({[k]: v * v}))).
blackcatweb

117

Нет нативных методов, но lodash # mapValues ​​отлично справится с этой задачей.

_.mapValues({ 'a': 1, 'b': 2, 'c': 3} , function(num) { return num * 3; });
// → { 'a': 3, 'b': 6, 'c': 9 }

6
Нет необходимости добавлять дополнительный HTTP-вызов и дополнительную библиотеку только для одной функции. В любом случае, этот ответ устарел, и вы можете просто позвонить, Object.entries({a: 1, b: 2, c: 3})чтобы получить массив.

11
Почему получение массива полезно? Требование было для сопоставленного объекта. Также не подразумевается HTTP-вызов при использовании Lodash, популярной библиотеки, специально созданной для такого типа вещей, хотя есть довольно много вещей, которые в ES6 в последнее время исключают необходимость использования библиотек служебных функций.
corse32

2
дополнительный HTTP-вызов, чтобы вытащить Лодаш Если это ваш единственный вариант использования, то на самом деле это избыточное убийство. Тем не менее, имеет смысл иметь этот ответ, потому что Lodash - такая широко распространенная библиотека.
igorsantos07

2
не говоря уже о том, что вы должны его использовать, _.map()чтобы получить ключ в качестве второго аргумента - как того требует OP.
igorsantos07

_.mapValues()также предоставляет ключ в качестве второго аргумента. Также _.map()не будет работать здесь, поскольку он возвращает только массивы, а не объекты:_.map({ 'a': 1, 'b': 2, 'c': 3}, n => n * 3) // [3, 6, 9]
MeltedPenguin

57

Это довольно легко написать:

Object.map = function(o, f, ctx) {
    ctx = ctx || this;
    var result = {};
    Object.keys(o).forEach(function(k) {
        result[k] = f.call(ctx, o[k], k, o); 
    });
    return result;
}

с примером кода:

> o = { a: 1, b: 2, c: 3 };
> r = Object.map(o, function(v, k, o) {
     return v * v;
  });
> r
{ a : 1, b: 4, c: 9 }

NB: эта версия также позволяет вам (опционально) устанавливать thisконтекст для обратного вызова, как и Arrayметод.

РЕДАКТИРОВАТЬ - изменено, чтобы удалить использование Object.prototype, чтобы оно не конфликтовало с каким-либо существующим свойством, названным mapв объекте.


3
Хорошо. :) Комментарий по этому вопросу привел меня сюда. Я получу +1, поскольку принятый ответ явно неправильно map, но я должен сказать, что изменение Object.prototypeменя не устраивает, даже если оно не перечисляемо.
JLRishe

2
@JLRishe Спасибо. ИМХО в ES5 действительно больше нет никаких причин не изменять, Object.prototypeкроме (теоретического) риска столкновения с другими методами. FWIW, я не могу понять, как другой ответ получил столько же голосов, сколько он имеет, это совершенно неправильно.
Альнитак

7
@JLRishe ты старый чудак ... идите в ногу со временем и модифицируйте этот прототип!
Ник Мэннинг

12
@NickManning Вы, юные бродяги и ваши безответственные поступки. Убирайся с моего газона!
JLRishe

3
Это уже не работает o = {map: true, image: false}. Вы рискуете просто написать o.map(fn)вместоmap(o,fn)
fregante

25

Вы можете использовать Object.keysи затем forEachвозвращенный массив ключей:

var myObject = { 'a': 1, 'b': 2, 'c': 3 },
    newObject = {};
Object.keys(myObject).forEach(function (key) {
    var value = myObject[key];
    newObject[key] = value * value;
});

Или более модульным способом:

function map(obj, callback) {
    var result = {};
    Object.keys(obj).forEach(function (key) {
        result[key] = callback.call(obj, obj[key], key, obj);
    });
    return result;
}

newObject = map(myObject, function(x) { return x * x; });

Обратите внимание, что Object.keysвозвращает массив, содержащий только собственные перечисляемые свойства объекта, поэтому он ведет себя как for..inцикл с hasOwnPropertyпроверкой.


20

Это прям бразильцы, и все в сообществе JS знают это. Там должна быть такая функциональность:

const obj1 = {a:4, b:7};
const obj2 = Object.map(obj1, (k,v) => v + 5);

console.log(obj1); // {a:4, b:7}
console.log(obj2); // {a:9, b:12}

вот наивная реализация:

Object.map = function(obj, fn, ctx){

    const ret = {};

    for(let k of Object.keys(obj)){
        ret[k] = fn.call(ctx || null, k, obj[k]);
    });

    return ret;
};

Это очень раздражает, когда приходится все время реализовывать самостоятельно;)

Если вы хотите что-то более сложное, которое не мешает классу Object, попробуйте это:

let map = function (obj, fn, ctx) {
  return Object.keys(obj).reduce((a, b) => {
    a[b] = fn.call(ctx || null, b, obj[b]);
    return a;
  }, {});
};


const x = map({a: 2, b: 4}, (k,v) => {
    return v*2;
});

но безопасно добавить эту функцию карты в Object, просто не добавляйте в Object.prototype.

Object.map = ... // fairly safe
Object.prototype.map ... // not ok

Почему вы добавляете эту функцию в глобальный Objectобъект? Просто есть const mapObject = (obj, fn) => { [...]; return ret; }.
Амберлампс

7
Потому что это должен быть метод на объекте :)
Александр Миллс

1
Пока мы не связываемся с Object.prototype, у нас все должно быть в порядке
Александр Миллс

1
Если карта была просто случайной неопределенной вещью, конечно. Но .mapобычно понимается как интерфейс Functor, и не существует единого определения Functor для «Объекта», потому что практически все в javascript может быть объектом, включая вещи со своими собственными очень четкими и определенными интерфейсами / логикой .map.
Dtipson

1
На мой взгляд, первым аргументом обратного вызова должен быть сам итеративный элемент, а не ключ. Например, я предполагаю, что он должен дать мне тот же объект, как это обычно происходит с общей картой: Object.map ({prop: 1}, el => el), но он возвращает ключи со значением с тем же именем ключа ...
Глеб Долзиков

17

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

Вы можете использовать map для возврата нового массива из объекта следующим образом:

var newObject = Object.keys(myObject).map(function(key) {
   return myObject[key];
});

6
Это не работает удаленно - он просто возвращает массив значений объекта, а не новый объект. Я не могу поверить, что у него так много голосов.
Альнитак

1
Вы правы, он не отвечает на вопрос правильно, так как они искали ответ для сопоставления объекта и объекта. Я пришел сюда в поисках ответа для отображения объекта в массив и получил эту страницу в результате, поэтому я оставлю свой ответ и отредактирую его, чтобы отразить, что это альтернативный ответ для людей, которые, возможно, попали сюда в результате ищет отображение объекта в массивах.
JoeTron

5
В ES7 это будет тривиальноObject.values(myObject)
Альнитак,

1
Это приятно знать, но некоторые люди все еще должны использовать устаревший код.
JoeTron

const obj = {foo: 'bar', baz: 42}; console.log (Object.entries (OBJ)); // [['foo', 'bar'], ['baz', 42]]
Bashirpour

12

Принятый ответ имеет два недостатка:

  • Это неправильно Array.prototype.reduce, потому что сокращение означает изменение структуры составного типа, чего не происходит в этом случае.
  • Это не особо многоразового использования

Функциональный подход ES6 / ES2015

Обратите внимание, что все функции определены в карри.

// small, reusable auxiliary functions

const keys = o => Object.keys(o);

const assign = (...o) => Object.assign({}, ...o);

const map = f => xs => xs.map(x => f(x));

const mul = y => x => x * y;

const sqr = x => mul(x) (x);


// the actual map function

const omap = f => o => {
  o = assign(o); // A
  map(x => o[x] = f(o[x])) (keys(o)); // B
  return o;
};


// mock data

const o = {"a":1, "b":2, "c":3};


// and run

console.log(omap(sqr) (o));
console.log(omap(mul(10)) (o));

  • В строке А oпереназначается. Поскольку Javascript передает ссылочные значения путем совместного использования , создается неглубокая копия o. Теперь мы можем мутировать oвнутри omapбез мутацииo в родительской области видимости.
  • В строке B mapвозвращаемое значение игнорируется, поскольку mapвыполняет мутацию o. Поскольку этот побочный эффект остается внутри omapи не виден в родительской области, он полностью приемлем.

Это не самое быстрое решение, но декларативное и многоразовое. Вот та же реализация, что и в одной строке, краткая, но менее читаемая:

const omap = f => o => (o = assign(o), map(x => o[x] = f(o[x])) (keys(o)), o);

Приложение - почему объекты не повторяются по умолчанию?

ES2015 определяет итератор и итерируемые протоколы. Но объекты все еще не повторяемы и, следовательно, не отображаются. Причина заключается в смешении данных и уровня программы .


1
Похоже, немного перегружен; Я считаю 6 функций для того, что по сути является петлей. Другие решения намного проще, и принятый ответ также в 5 раз быстрее.
fregante

4
@ bfred.it Какова цель вашего комментария? Если вам нравится микрооптимизация, вы также не должны использовать ее Array.prototype.reduce. Все, что я хочу проиллюстрировать здесь, это то, что принятый ответ как-то «злоупотребляет» Array.prototype.reduceи как может быть реализовано чисто функциональное отображение. Если вас не интересует функциональное программирование, просто проигнорируйте мой ответ.

2
«сокращение означает изменение структуры составного типа». Я не думаю, что это правда. Array.reduce, который создает новый массив точно такой же длины или даже точно таких же значений, является вполне допустимым. Reduce можно использовать для воссоздания функциональности карты и / или фильтра (или того и другого, как в сложном преобразователе, для которого основой является Reduce). В FP это тип сгиба, и фолд с тем же типом все еще является фолдом.
Dtipson

2
@Dtipson Ты абсолютно прав. A foldявляется более общим, чем map(функтор). Тем не менее, это были мои знания с середины 2016 года. Это пол вечности: D

1
Ха, да Мне потребовались годы, чтобы понять, насколько взаимосвязаны все эти вещи и более обширная экосистема терминов и интерфейсов. Вещи, которые кажутся такими простыми, оказываются невероятно сложными, а некоторые вещи, которые кажутся невероятно сложными, абстрагируются довольно аккуратно. :)
Dtipson

10

JavaScript только что получил новый Object.fromEntriesметод.

пример

function mapObject (obj, fn) {
  return Object.fromEntries(
    Object
      .entries(obj)
      .map(fn)
  )
}

const myObject = { a: 1, b: 2, c: 3 }
const myNewObject = mapObject(myObject, ([key, value]) => ([key, value * value]))
console.log(myNewObject)

объяснение

Приведенный выше код преобразует объект во вложенный массив ( [[<key>,<value>], ...]), который можно отобразить. Object.fromEntriesпреобразует массив обратно в объект.

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

Документация

Поддержка браузера

Object.fromEntriesв настоящее время поддерживается только этими браузерами / движками , тем не менее, есть доступные полифилы (например, @ babel / polyfill ).


2
В основном Object.fromEntriesэто противоположность Object.entries. Object.entriesпреобразует объект в список пар ключ-значение. Object.fromEntriesпреобразует список пар ключ-значение в объект.
Орад

8

Минимальная версия (es6):

Object.entries(obj).reduce((a, [k, v]) => (a[k] = v * v, a), {})

Я думаю, что это неправильно из-за опечатки, я думаю, вы имеете в виду => Object.entries(obj).reduce((a, [k, v]) => [a[k] = v * v, a], {}), угловые скобки вместо скобок.
Александр Миллс

@AlexanderMills, мой код правильный. Узнайте больше об операторе запятой в скобках: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Yukulélé

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

1
в основном мой код эквивалентен:Object.entries(obj).reduce((a, [k, v]) => {a[k] = v * v; return a}, {})
Yukulélé

1
Отличный ответ. Object.entries().reduceэто лучшее решение , которое я думаю , что с ES6 + .Would получить больше upvotes , если он использовал returnзаявление в редукторе , а не implicit returnи commaоператора - что аккуратный , но трудно читать ИМХО
Drenai

7

Для максимальной производительности.

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

// example object
var obj = {a: 1, b: 2, c: 'something'};

// caching map
var objMap = new Map(Object.entries(obj));

// fast iteration on Map object
objMap.forEach((item, key) => {
  // do something with an item
  console.log(key, item);
});

Object.entries уже работает в Chrome, Edge, Firefox и бета-версии Opera, поэтому эта функция ориентирована на будущее. Он из ES7, поэтому заполняйте его https://github.com/es-shims/Object.entries для IE, где он не работает.


Как насчет использования Разрушения ?
Константин Ван

1
@ K._ Раньше это было очень медленно, но я не знаю о производительности сейчас. Похоже, что V8 v 5.6 представил некоторые оптимизации для деструктуризации, но это должно быть измерено v8project.blogspot.co.uk
Pawel

Или менее производительный es7, но неизменный:const fn = v => v * 2; const newObj = Object.entries(myObject).reduce((acc, [k,v]) => Object.assign({}, acc, {[k]: fn(v)}), {});
mikebridge

Мне интересно, почему ни один из других ответов не использует Object.entries. Это намного чище, чем перебирать ключи и использовать obj [key].
corwin.amber

6

Вы можете использовать mapметод и forEachна массивах, но если вы хотите использовать его наObject тогда, вы можете использовать его с поворотом, как показано ниже:

Использование Javascript (ES6)

var obj = { 'a': 2, 'b': 4, 'c': 6 };   
Object.entries(obj).map( v => obj[v[0]] *= v[1] );
console.log(obj); //it will log as {a: 4, b: 16, c: 36}

var obj2 = { 'a': 4, 'b': 8, 'c': 10 };
Object.entries(obj2).forEach( v => obj2[v[0]] *= v[1] );
console.log(obj2); //it will log as {a: 16, b: 64, c: 100}

Использование jQuery

var ob = { 'a': 2, 'b': 4, 'c': 6 };
$.map(ob, function (val, key) {
   ob[key] *= val;
});
console.log(ob) //it will log as {a: 4, b: 16, c: 36}

Или вы можете использовать другие циклы также как $.eachметод, как показано ниже:

$.each(ob,function (key, value) {
  ob[key] *= value;
});
console.log(ob) //it will also log as {a: 4, b: 16, c: 36}

Где $jquery?
masterxilo

Да, вы можете использовать как $, так и jQuery
Харицин Гохил

1
@ Рональд, я неправильно понял, посмотрите мой обновленный ответ, спасибо, что не понизили голосование, но действительно осознали мою ошибку, спасибо за улучшение сообщества.
Харицин Гохил

@bcoughlan Да, это именно то, что я тоже написал в комментарии, это квадраты чисел, что ты тогда хочешь?
Харицин Гохил

1
@HaritsinhGohil Я понял, что он критиковал вас за решение jQuery. Это правда, что в наши дни есть какой-то стимул отойти от jQuery, и, конечно, есть Node, но если OP не запрашивает конкретно JS или Node, я думаю, что можно предположить, что кто-то работает в веб-среде и что jQuery почти наверняка будет доступен.
abalter

4

map functionНе существует на Object.prototypeоднако вы можете подражать ему , как так

var myMap = function ( obj, callback ) {

    var result = {};

    for ( var key in obj ) {
        if ( Object.prototype.hasOwnProperty.call( obj, key ) ) {
            if ( typeof callback === 'function' ) {
                result[ key ] = callback.call( obj, obj[ key ], key, obj );
            }
        }
    }

    return result;

};

var myObject = { 'a': 1, 'b': 2, 'c': 3 };

var newObject = myMap( myObject, function ( value, key ) {
    return value * value;
});

Проблемы производительности: typeof callback === 'function'абсолютно излишни. 1) mapне имеет смысла без обратного вызова 2) если callbackне функция, ваша mapреализация просто выполняет бессмысленный цикл for. Кроме того, Object.prototype.hasOwnProperty.call( obj, key )это немного излишне.
Анхзет

3

Я натолкнулся на это как на первый элемент в поиске Google, пытаясь научиться делать это, и подумал, что поделюсь с другими людьми, которые недавно нашли это решение, которое я нашел, использующее неизменяемый пакет npm.

Я думаю, что им интересно поделиться, потому что immutable использует ситуацию EXACT OP в своей собственной документации - ниже не мой собственный код, а взятый из текущей документации immutable-js:

const { Seq } = require('immutable')
const myObject = { a: 1, b: 2, c: 3 }
Seq(myObject).map(x => x * x).toObject();
// { a: 1, b: 4, c: 9 } 

Не то, чтобы у Seq были другие свойства («Seq описывает ленивую операцию, позволяющую им эффективно объединять в цепочку все методы сбора данных более высокого порядка (такие как map и filter), не создавая промежуточные коллекции»), и что некоторые другие данные immutable-js Структуры также могут выполнять работу достаточно эффективно.

Любой, кто использует этот метод, конечно, должен npm install immutableи может захотеть прочитать документы:

https://facebook.github.io/immutable-js/


3

РЕДАКТИРОВАТЬ: канонический способ использования новых функций JavaScript это -

const identity = x =>
  x

const omap = (f = identity, o = {}) =>
  Object.fromEntries(
    Object.entries(o).map(([ k, v ]) =>
      [ k, f(v) ]
    )
  )

Где oкакой-то объект и fваша функция отображения. Или мы можем сказать, что, учитывая функцию from a -> bи объект со значениями типа a, создаем объект со значениями типа b. Как подпись псевдо-типа -

// omap : (a -> b, { a }) -> { b }

Оригинальный ответ был написан, чтобы продемонстрировать мощный комбинатор, mapReduceкоторый позволяет нам думать о нашем преобразовании по-другому

  1. m, отображение функция - дает вам возможность преобразовать входящий элемент до того, как…
  2. r, То сокращение функция - это функция , сочетает в себе аккумулятор с результатом отображенного элемента

Интуитивно понятно, mapReduceсоздает новый редуктор, к которому мы можем подключиться напрямую Array.prototype.reduce. Но что еще более важно, мы можем реализовать нашу реализацию объектного функтора omapпросто, используя объект monoid, Object.assignи {}.

const identity = x =>
  x
  
const mapReduce = (m, r) =>
  (a, x) => r (a, m (x))

const omap = (f = identity, o = {}) =>
  Object
    .keys (o)
    .reduce
      ( mapReduce
          ( k => ({ [k]: f (o[k]) })
          , Object.assign
          )
      , {}
      )
          
const square = x =>
  x * x
  
const data =
  { a : 1, b : 2, c : 3 }
  
console .log (omap (square, data))
// { a : 1, b : 4, c : 9 }

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

k => ({ [k]: f (o[k]) })

Что говорит, учитывая известный объект oи некоторый ключ k, создает объект, и его вычисленное свойство kявляется результатом вызова fзначения ключа,o[k] .

Мы получим представление о mapReduceпотенциале секвенирования, если сначала рассмотримoreduce

// oreduce : (string * a -> string * b, b, { a }) -> { b }
const oreduce = (f = identity, r = null, o = {}) =>
  Object
    .keys (o)
    .reduce
      ( mapReduce
          ( k => [ k, o[k] ]
          , f
          )
      , r
      )

// omap : (a -> b, {a}) -> {b}
const omap = (f = identity, o = {}) =>
  oreduce
    ( mapReduce
        ( ([ k, v ]) =>
            ({ [k]: f (v) })
        , Object.assign
        )
    , {}
    , o
    )

Все работает одинаково, но omapтеперь может быть определено на более высоком уровне. Конечно новыйObject.entries это выглядит глупо, но упражнение все еще важно для ученика.

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


4
Давайте скорее использовать Mapи Map.entriesокей: D. Я устал от использования простых объектов в качестве типов картографических данных.

2
Я согласен, Mapследует использовать там, где это возможно, когда это возможно, но это не полностью заменяет необходимость использования этих процедур на простых объектах JS, хотя: D
Спасибо,

2

Основываясь на ответе @Amberlamps, вот полезная функция (как комментарий это выглядело некрасиво)

function mapObject(obj, mapFunc){
    return Object.keys(obj).reduce(function(newObj, value) {
        newObj[value] = mapFunc(obj[value]);
        return newObj;
    }, {});
}

и использование это:

var obj = {a:1, b:3, c:5}
function double(x){return x * 2}

var newObj = mapObject(obj, double);
//=>  {a: 2, b: 6, c: 10}

2
var myObject = { 'a': 1, 'b': 2, 'c': 3 };


Object.prototype.map = function(fn){
    var oReturn = {};
    for (sCurObjectPropertyName in this) {
        oReturn[sCurObjectPropertyName] = fn(this[sCurObjectPropertyName], sCurObjectPropertyName);
    }
    return oReturn;
}
Object.defineProperty(Object.prototype,'map',{enumerable:false});





newObject = myObject.map(function (value, label) {
    return value * value;
});


// newObject is now { 'a': 1, 'b': 4, 'c': 9 }

enumerableпо умолчаниюfalse
спасибо

2

Мой ответ в значительной степени основан на ответе с самым высоким рейтингом, и, надеюсь, все это понимают (тоже самое объяснение на моем GitHub). Вот почему его имплементация с картой работает:

Object.keys(images).map((key) => images[key] = 'url(' + '"' + images[key] + '"' +    
')');

Назначение функции - взять объект и изменить исходное содержимое объекта, используя метод, доступный для всех объектов (как объектов, так и массивов) без возврата массива. Почти все в JS является объектом, и по этой причине элементы, расположенные ниже по конвейеру наследования, могут технически использовать те, которые доступны для тех, кто находится на линии вверх (и наоборот).

Причина этого заключается в том, что функции .map возвращают массив, ТРЕБУЮЩИЙ, что вы предоставляете явный или неявный ВОЗВРАТ массива вместо простой модификации существующего объекта. По сути, вы заставляете программу думать, что объект является массивом, используя Object.keys, который позволит вам использовать функцию map, воздействуя на значения, с которыми связаны отдельные ключи (я фактически случайно возвратил массивы, но исправил это). Пока нет возврата в обычном смысле, не будет никакого массива, созданного с оригинальным объектом, все еще целым и измененным как запрограммированный.

Эта конкретная программа принимает объект с именем images, принимает значения его ключей и добавляет URL-теги для использования в другой функции. Оригинал это:

var images = { 
snow: 'https://www.trbimg.com/img-5aa059f5/turbine/bs-md-weather-20180305', 
sunny: 'http://www.cubaweather.org/images/weather-photos/large/Sunny-morning-east-   
Matanzas-city- Cuba-20170131-1080.jpg', 
rain: 'https://i.pinimg.com/originals/23/d8
/ab/23d8ab1eebc72a123cebc80ce32b43d8.jpg' };

... и модифицировано это:

var images = { 
snow: url('https://www.trbimg.com/img-5aa059f5/turbine/bs-md-weather-20180305'),     
sunny: url('http://www.cubaweather.org/images/weather-photos/large/Sunny-morning-   
east-Matanzas-city- Cuba-20170131-1080.jpg'), 
rain: url('https://i.pinimg.com/originals/23/d8
/ab/23d8ab1eebc72a123cebc80ce32b43d8.jpg') 
};

Первоначальная структура объекта остается неизменной, что обеспечивает нормальный доступ к свойству, если нет возврата. НЕ возвращайте массив, как обычно, и все будет хорошо. Цель - переназначить исходные значения (изображения [ключ]) на то, что нужно, а не на что-либо еще. Насколько я знаю, для предотвращения вывода массива ДОЛЖНА быть ПЕРЕОБРАЗОВАНИЕ изображений [ключ] и неявный или неявный запрос на возврат массива (назначение переменных делает это и дает сбой мне и обратно).

РЕДАКТИРОВАТЬ:

Собираюсь обратиться к своему другому методу относительно создания нового объекта, чтобы избежать изменения исходного объекта (и переназначение, по-видимому, все еще необходимо, чтобы избежать случайного создания массива в качестве вывода). Эти функции используют синтаксис стрелки и, если вы просто хотите создать новый объект для будущего использования.

const mapper = (obj, mapFn) => Object.keys(obj).reduce((result, key) => {
                result[key] = mapFn(obj)[key];
                return result;
            }, {});

var newImages = mapper(images, (value) => value);

Принцип работы этих функций выглядит следующим образом:

mapFn берет функцию, которая будет добавлена ​​позже (в этом случае (value) => value), и просто возвращает все, что там хранится, в качестве значения для этого ключа (или умножается на два, если вы измените возвращаемое значение, как он сделал) в mapFn ( объект) [ключ],

а затем переопределяет исходное значение, связанное с ключом, в результате [ключ] = mapFn (obj) [ключ]

и возвращает операцию, выполненную с результатом (аккумулятор, расположенный в скобках, инициированный в конце функции .reduce).

Все это выполняется на выбранном объекте, и ЕЩЕ НЕ МОЖЕТ быть неявный запрос возвращаемого массива, и он работает только при переназначении значений, насколько я могу судить. Это требует некоторой умственной гимнастики, но сокращает количество необходимых строк кода, как видно выше. Вывод точно такой же, как видно ниже:

{snow: "https://www.trbimg.com/img-5aa059f5/turbine/bs-   
md-weather-20180305", sunny: "http://www.cubaweather.org/images/weather-
photos/lmorning-east-Matanzas-city-Cuba-20170131-1080.jpg", rain: 
"https://i.pinimg.com/originals/23/d8
/ab/23d8ab1eebc72a123cebc80ce32b43d8.jpg"}

Имейте в виду, что это работает с NUMNUMERS. Вы МОЖЕТЕ Дублировать ЛЮБОЙ объект, ПРОСТО ВОЗВРАЩАЯ ЗНАЧЕНИЕ в функции mapFN.


2

Первая функция отвечает на вопрос. Создает карту.

Вторая функция не создает карту. Вместо этого он просматривает свойства в существующем объекте используя hasOwnProperty(). Эта альтернатива карты может быть лучшим решением в некоторых ситуациях.

var myObject = { 'a': 1, 'b': 2, 'c': 3 }


//creates a map (answers question, but function below is 
//much better in some situations)
var newObject = {};
makeMap();    
function makeMap() {
    for (var k in myObject) {
        var value = myObject[k];
        newObject[k] = value * value;
    }
    console.log(newObject); //mapped array
}


//Doesn't create a map, just applies the function to
//a specific property using existing data
function getValue(key) {
  for (var k in myObject) {
    if (myObject.hasOwnProperty(key)) {
      var value = myObject[key]
      return value * value; //stops iteration
    }
  }
}
Input: <input id="input" value="" placeholder="a, b or c"><br>
Output:<input id="output"><br>
<button onclick="output.value=getValue(input.value)" >Get value</button>


2
Это не отвечает на первоначальный вопрос - оно не отображает значения в новый объект с предоставленной функцией (то есть это не похоже на Array.prototype.map).
Мэтт Браун

@MattBrowne Я добавил второй метод, который создает, возможно, карту. Это медленно, но делает то, что запрашивает OP. Первый метод идеален во многих ситуациях, поэтому я использовал цикл и функции свойств.
Виктор Стоддард

1

Если вы заинтересованы в mapпинге не только значений, но и ключей, я написал Object.map(valueMapper, keyMapper), что ведет себя так:

var source = { a: 1, b: 2 };
function sum(x) { return x + x }

source.map(sum);            // returns { a: 2, b: 4 }
source.map(undefined, sum); // returns { aa: 1, bb: 2 }
source.map(sum, sum);       // returns { aa: 2, bb: 4 }

3
Я полагаю, потому что вы в основном только что предоставили ссылку в качестве ответа, который осуждается.
Крис Райт

Если когда - нибудь полезным для тех , кто: npm install @mattisg/object.map.
MattiSG

1

Мне нужна была версия, которая также позволяла изменять ключи (на основе ответов @Amberlamps и @yonatanmn);

var facts = [ // can be an object or array - see jsfiddle below
    {uuid:"asdfasdf",color:"red"},
    {uuid:"sdfgsdfg",color:"green"},
    {uuid:"dfghdfgh",color:"blue"}
];

var factObject = mapObject({}, facts, function(key, item) {
    return [item.uuid, {test:item.color, oldKey:key}];
});

function mapObject(empty, obj, mapFunc){
    return Object.keys(obj).reduce(function(newObj, key) {
        var kvPair = mapFunc(key, obj[key]);
        newObj[kvPair[0]] = kvPair[1];
        return newObj;
    }, empty);
}

factObject =

{
"asdfasdf": {"color":"red","oldKey":"0"},
"sdfgsdfg": {"color":"green","oldKey":"1"},
"dfghdfgh": {"color":"blue","oldKey":"2"}
}

Редактировать: небольшое изменение для передачи в начальный объект {}. Позволяет ему быть [] (если ключи целые)


1

Я специально хотел использовать ту же функцию, что и для массивов для одного объекта, и хотел, чтобы она была простой. Это сработало для меня:

var mapped = [item].map(myMapFunction).pop();

это ничем не отличается отvar mapped = myMapFunction(item)
спасибо

Это намного проще, чем все другие решения "ракетостроения", которые я видел здесь. Спасибо за обмен :)
Юрий Тейшейра

1

Чтобы более точно реагировать на то, что именно запрашивал ОП, ОП хочет объект:

myObject = { 'a': 1, 'b': 2, 'c': 3 }

иметь метод карты myObject.map,

похож на Array.prototype.map, который будет использоваться следующим образом:

newObject = myObject.map (function (value, label) {
    возвращаемое значение * значение;
});
// newObject теперь {'a': 1, 'b': 4, 'c': 9}

Имхо лучше (измеряется в терминах к « близко к тому , что просят » + «не требуется ES {5,6,7} без необходимости») ответ был бы:

myObject.map = function mapForObject(callback)
{
  var result = {};
  for(var property in this){
    if(this.hasOwnProperty(property) && property != "map"){
      result[property] = callback(this[property],property,this);
    }
  }
  return result;
}

Приведенный выше код избегает преднамеренного использования каких-либо языковых функций, доступных только в последних выпусках ECMAScript. С помощью кода выше проблема может быть решена следующим образом:

myObject = { 'a': 1, 'b': 2, 'c': 3 };

myObject.map = function mapForObject(callback)
{
  var result = {};
  for(var property in this){
    if(this.hasOwnProperty(property) && property != "map"){
      result[property] = callback(this[property],property,this);
    }
  }
  return result;
}

newObject = myObject.map(function (value, label) {
  return value * value;
});
console.log("newObject is now",newObject);
альтернативный тестовый код здесь

Помимо того, что некоторые нахмурились, была бы возможность вставить решение в цепочку прототипов следующим образом.

Object.prototype.map = function(callback)
{
  var result = {};
  for(var property in this){
    if(this.hasOwnProperty(property)){
      result[property] = callback(this[property],property,this);
    }
  }
  return result;
}

Что-то, что при тщательном надзоре не должно иметь каких-либо побочных эффектов и не влиять на mapметоды других объектов (например, массивов map).



1

Определить функцию mapEntries.

mapEntries принимает функцию обратного вызова, которая вызывается для каждой записи в объекте со значением параметра, ключом и объектом. Он должен вернуть новое значение.

mapEntries должен вернуть новый объект с новыми значениями, возвращенными из обратного вызова.

Object.defineProperty(Object.prototype, 'mapEntries', {
  enumerable: false,
  value: function (mapEntriesCallback) {
    return Object.fromEntries(
      Object.entries(this).map(
        ([key, value]) => [key, mapEntriesCallback(value, key, this)]
      )
    )
  }
})


// Usage example:

var object = {a: 1, b: 2, c: 3}
var newObject = object.mapEntries(value => value * value)
console.log(newObject)
//> {a: 1, b: 4, c: 9}

Редактировать: в предыдущей версии не указывалось, что это не перечисляемое свойство


0

Эй, написал небольшую картографическую функцию, которая может помочь.

    function propertyMapper(object, src){
         for (var property in object) {   
           for (var sourceProp in src) {
               if(property === sourceProp){
                 if(Object.prototype.toString.call( property ) === '[object Array]'){
                   propertyMapper(object[property], src[sourceProp]);
                   }else{
                   object[property] = src[sourceProp];
                }
              }
            }
         }
      }

1
Я предполагаю, что вы имели в виду «я» вместо «Эй»
Карло

0

Другой подход заключается в использовании пользовательской функции json stringify, которая также может работать с глубокими объектами. Это может быть полезно, если вы собираетесь отправить его на сервер в любом случае как json

const obj = { 'a': 1, 'b': 2, x: {'c': 3 }}
const json = JSON.stringify(obj, (k, v) => typeof v === 'number' ? v * v : v)

console.log(json)
console.log('back to json:', JSON.parse(json))


0

Я работаю только со строками, чтобы уменьшить количество исключений:

Object.keys(params).map(k => typeof params[k] == "string" ? params[k] = params[k].trim() : null);

0

Object Mapper в TypeScript

Мне нравятся примеры, которые используют, Object.fromEntriesтакие как этот , но тем не менее, они не очень просты в использовании. Ответы, которые используют, Object.keysа затем ищут, на keyсамом деле делают несколько поисков, которые могут не понадобиться.

Хотелось бы, чтобы была Object.mapфункция, но мы можем создать свою собственную и вызывать ее objectMapс возможностью изменять keyи value:

Использование (JavaScript):

const myObject = { 'a': 1, 'b': 2, 'c': 3 };

// keep the key and modify the value
let obj = objectMap(myObject, val => val * 2);
// obj = { a: 2, b: 4, c: 6 }


// modify both key and value
obj = objectMap(myObject,
    val => val * 2 + '',
    key => (key + key).toUpperCase());
// obj = { AA: '2', BB: '4', CC: '6' }

Код (TypeScript):

interface Dictionary<T> {
    [key: string]: T;
}

function objectMap<TValue, TResult>(
    obj: Dictionary<TValue>,
    valSelector: (val: TValue, obj: Dictionary<TValue>) => TResult,
    keySelector?: (key: string, obj: Dictionary<TValue>) => string,
    ctx?: Dictionary<TValue>
) {
    const ret = {} as Dictionary<TResult>;
    for (const key of Object.keys(obj)) {
        const retKey = keySelector
            ? keySelector.call(ctx || null, key, obj)
            : key;
        const retVal = valSelector.call(ctx || null, obj[key], obj);
        ret[retKey] = retVal;
    }
    return ret;
}

Если вы не используете TypeScript, скопируйте приведенный выше код в TypeScript Playground, чтобы получить код JavaScript.

Кроме того , почему я поставил keySelectorпосле того, как valSelectorв списке параметров, потому , что это не является обязательным.

* Некоторые кредиты идут на ответ Александра Миллса .


0
const mapObject = (targetObject, callbackFn) => {
    if (!targetObject) return targetObject;
    if (Array.isArray(targetObject)){
        return targetObject.map((v)=>mapObject(v, callbackFn))
    }
    return Object.entries(targetObject).reduce((acc,[key, value]) => {
        const res = callbackFn(key, value);
        if (!Array.isArray(res) && typeof res ==='object'){
            return {...acc, [key]: mapObject(res, callbackFn)}
        }
        if (Array.isArray(res)){
            return {...acc, [key]: res.map((v)=>mapObject(v, callbackFn))}
        }
        return {...acc, [key]: res};
    },{})
};
const mapped = mapObject(a,(key,value)=> {
    if (!Array.isArray(value) && key === 'a') return ;
    if (!Array.isArray(value) && key === 'e') return [];
    if (!Array.isArray(value) && key === 'g') return value * value;
    return value;
});
console.log(JSON.stringify(mapped)); 
// {"b":2,"c":[{"d":2,"e":[],"f":[{"g":4}]}]}

Эта функция рекурсивно проходит через объект и массивы объектов. Атрибуты могут быть удалены, если они возвращены неопределенными

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