Avatar ·

Функция map для объектов в JavaScript (аналог Array.prototype.map)

Всем привет! Я пишу на JavaScript уже несколько месяцев, и у меня возникла проблема. Я знаю, что у массивов есть замечательный метод `.map()`, который позволяет преобразовать каждый элемент и получить новый массив. Например: ```javascript const numbers = [1, 2, 3]; const doubled = numbers.map(n => n * 2); // doubled = [2, 4, 6] ``` Но что делать, если мне нужно сделать то же самое с объектом? У меня есть объект с данными пользователей: ```javascript const users = { alice: { age: 25, city: 'Moscow' }, bob: { age: 30, city: 'SPb' }, charlie: { age: 35, city: 'Kazan' } }; ``` Я хочу получить новый объект, где каждому пользователю добавится поле `isAdult: true`, потому что всем больше 18 лет. Но у объектов нет метода `.map()`! Я пробовал использовать `Object.keys()` и цикл `for...of`, но это выглядит очень громоздко. Может быть, есть какой-то элегантный способ, похожий на работу с массивами? Или нужно писать свою функцию? Буду благодарен за примеры кода и объяснения. Заранее спасибо! <<>>

Решение: кастомная функция mapObject

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

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

function mapObject(obj, callback) {
  const result = {};
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      result[key] = callback(obj[key], key, obj);
    }
  }
  return result;
}

Использование с вашим примером:

const users = {
  alice: { age: 25, city: 'Moscow' },
  bob: { age: 30, city: 'SPb' },
  charlie: { age: 35, city: 'Kazan' }
};

const updatedUsers = mapObject(users, user => ({
  ...user,
  isAdult: user.age >= 18
}));

console.log(updatedUsers);
// {
//   alice: { age: 25, city: 'Moscow', isAdult: true },
//   bob: { age: 30, city: 'SPb', isAdult: true },
//   charlie: { age: 35, city: 'Kazan', isAdult: true }
// }

Почему это работает?

  • for...in — перебирает все перечисляемые ключи объекта (включая унаследованные, поэтому мы проверяем hasOwnProperty).
  • callback получает три аргумента: текущее значение, ключ и исходный объект — точно как в Array.prototype.map.
  • Новый объект создаётся с теми же ключами, но с преобразованными значениями.

Современный вариант с Object.entries()

Если вы используете ES2017+, можно сделать короче и без проверки hasOwnProperty:

function mapObject(obj, callback) {
  return Object.fromEntries(
    Object.entries(obj).map(([key, value]) => [key, callback(value, key, obj)])
  );
}

Этот подход:

  • Object.entries(obj) — возвращает массив пар [key, value].
  • .map() — применяет колбэк к каждой паре.
  • Object.fromEntries() — собирает массив пар обратно в объект.

Важные замечания

  • Порядок ключей — в современных движках строковые ключи сохраняют порядок вставки, но для числовых ключей порядок может отличаться.
  • Не мутируйте исходный объект — всегда возвращайте новый объект, это функциональный подход.
  • Производительность — для больших объектов цикл for...in может быть быстрее, чем Object.entries + map, но разница обычно незаметна.

Альтернатива: библиотека Lodash

Если вы используете lodash, там есть готовая функция _.mapValues:

const updatedUsers = _.mapValues(users, user => ({
  ...user,
  isAdult: user.age >= 18
}));

Она делает ровно то же самое, что мы написали выше. Но для образовательных целей полезно понимать, как это работает «под капотом».

<<>>

Другой подход: использование reduce

Можно также использовать Object.keys() в комбинации с reduce — это более функциональный стиль, который часто встречается в коде:

function mapObject(obj, callback) {
  return Object.keys(obj).reduce((acc, key) => {
    acc[key] = callback(obj[key], key, obj);
    return acc;
  }, {});
}

// Пример:
const users = {
  alice: { age: 25 },
  bob: { age: 30 }
};
const withAdults = mapObject(users, u => ({...u, adult: u.age >= 18}));
console.log(withAdults);
// { alice: { age: 25, adult: true }, bob: { age: 30, adult: true } }

Этот вариант:

  • Не требует проверки hasOwnProperty, так как Object.keys() возвращает только собственные ключи.
  • Чисто функциональный — нет побочных эффектов.
  • Легко читается, если вы знакомы с reduce.

Когда какой вариант выбрать?

  • for...in — если нужна максимальная производительность и совместимость со старыми браузерами.
  • Object.entries + fromEntries — если пишете современный код и цените лаконичность.
  • reduce — если привыкли к функциональному стилю и используете reduce в других местах.

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