функція відображення об'єктів (замість масивів)


1064

У мене є об’єкт:

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замість цього.
Оріол

14
Дивно мені, що JS не передбачив цього надзвичайно рудиментарного завдання.
jchook

3
@Oriol Ви впевнені в цьому? Згідно з веб-документами MDN, упорядкування елементів масиву відповідає між Object.keysі Object.getOwnPropertyNames. Дивіться developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Барт

@Bart Порядок Object.keysзалежить від реалізації. Дивіться stackoverflow.com/a/30919039/1529630
Oriol

4
@Oriol Ви не повинні покладатися на порядок клавіш в об'єктах, так що це не має жодного відношення до питання - оскільки він ніколи не вказував, що наказ має значення для нього. І якщо наказ має значення для нього, він взагалі не повинен використовувати об’єкт.
BT

Відповіді:


1540

Там немає рідної 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
Так, я б не рекомендував своє рішення для такої проблеми. Тому я оновив свою відповідь альтернативним, кращим рішенням.
Бурштинові світильники

1
Я оновив свою відповідь - здається, (перечитавши питання), що ОП хоче нового об’єкта з тими ж клавішами, де значення були мутовані за допомогою функції, що постачається, а не для зміни оригінального об'єкта на місці.
Альнітак

13
@KhalilRavanna Я думаю, що ви неправильно прочитали тут код - ця відповідь використовується mapнеправильно, оскільки вона не робить return- це зловживає mapтак, ніби це forEachдзвінок. Якщо він насправді це зробив, return myObject[value] * 2 то результатом буде масив, що містить вихідні значення, подвоєні, замість об'єкта, що містить оригінальні ключі з подвоєними значеннями, останнє, очевидно, те, про що вимагала ОП.
Альнітак

3
@Amberlamps з вашим .reduceприкладом все в порядку, але IMHO все-таки далекий від зручності .mapдля об'єктів, оскільки ваш .reduceзворотний виклик не тільки повинен відповідати тому, як він .reduceпрацює, але і вимагає, щоб він myObjectбув доступний у лексичній області. Останнє, зокрема, унеможливлює просто передачу посилання на функцію у зворотному дзвінку, вимагаючи замість цього анонімну функцію.
Альнітак

8
TBH Я б скоріше підтримав відповідь, що лише (або спочатку) надав друге рішення, представлене у цій відповіді. Перший працює і не обов'язково помиляється, але, як заявили інші, мені не подобається, що карта використовується таким чином. Коли я бачу карту, мій розум автоматично замислюється над "незмінною структурою даних"
mjohnsonengr

307

Як щодо одного вкладиша з негайним призначенням змінної в простому JS ( ES6 / ES2015 )?

Використання синтаксису оператора розповсюдження та обчисленого синтаксису імен ключа :

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

jsbin

Інша версія, що використовує зменшення:

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

115

Немає рідних методів, але 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})щоб отримати масив.

10
Чому отримання масиву корисно? Вимога полягала в відображенні об'єкта. Крім того, не передбачається виклик HTTP у використанні Lodash, популярної бібліотеки, по суті побудованої для цього типу речей, хоча є досить багато речей, які ES6 за останній час певною мірою виключають необхідність використання бібліотек функцій утиліт.
corse32

2
додатковий дзвінок HTTP, щоб витягнути Лодаша, я думаю. Якщо це ваш єдиний випадок використання, то справді це надмір. Тим не менш, є сенс, щоб ця відповідь для Лодаша була такою поширеною бібліотекою.
igorsantos07

2
не кажучи вже про те, що ви насправді повинні використовувати, _.map()щоб ви отримали ключ як другий аргумент - як того вимагає ОП.
igorsantos07

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 }

Примітка: ця версія також дозволяє (необов'язково) задавати thisконтекст зворотного дзвінка, як і Arrayметод.

EDIT - змінено для видалення використання Object.prototype, щоб переконатися, що воно не зіткнеться з будь-яким наявним властивістю, названим mapна об'єкті.


3
Добре. :) Коментар до цього питання привів мене сюди. Я поставив +1 цьому, оскільки прийнята відповідь явно неправдиво map, але мушу сказати, що модифікація Object.prototypeне відповідає мені, навіть якщо вона не перелічена.
JLRishe

2
@JLRishe Дякую IMHO в 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
Тому що це повинен бути метод на Object :)
Олександр Міллз

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

1
Якщо карта була лише випадковою невиразною річчю, обов'язково. Але, .mapяк правило, це розуміється як інтерфейс Functor, і немає єдиного визначення Functor для "Object", оскільки практично все в 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, який створює новий масив точно такої ж довжини або навіть однакових значень, є цілком законним. Зменшення може бути використане для відновлення функціональності карти та / або фільтра (або обох, як у складному перетворювачі, основою для якого є зменшення). У 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 та beta 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
Haritsinh Gohil

1
@Ronald Я неправильно зрозумів, дивіться мою оновлену відповідь, дякую, що не схиляюсь, але по-справжньому усвідомлюю свою помилку, дякую за вдосконалення спільноти.
Харіцин Гохіль

@bcoughlan так, це саме те, що я написав у коментарі, це квадратування цифр, що ви хочете тоді?
Харіцин Гохіль

1
@HaritsinhGohil Я вважав, що він критикує вас за надання рішення jQuery. Це правда, що в цей час є певний поштовх відійти від jQuery, і, звичайно, є Вузол, але якщо ОП не запитує спеціально для 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реалізація просто працює безглуздо для-циклу. Крім того, Object.prototype.hasOwnProperty.call( obj, key )є трохи надмірним.
ankhzet

3

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

Я думаю, що цікаво поділитися тим, що незмінний використовує ТОЧНУ ситуацію з ОП у власній документації - наступне - це не мій власний код, а витягнутий з поточної документації на незмінний 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 описує ледачу операцію, що дозволяє їм ефективно використовувати ланцюжок всіх методів збору вищого порядку (таких як карта і фільтр), не створюючи проміжні колекції"), а також деякі інші незмінні дані js структури також можуть зробити цю роботу досить ефективно.

Кожен, хто використовує цей метод, звичайно, повинен npm install immutableі, можливо, захоче прочитати документи:

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


3

EDIT: Канонічний спосіб використання новіших функцій JavaScript -

const identity = x =>
  x

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

Де oзнаходиться якийсь об’єкт і fє ваша функція відображення. Або ми могли б сказати, задаючи функцію від a -> bта об'єкт зі значеннями типу a, створювати об’єкт зі значеннями типу b. Як підпис псевдотипу -

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

Оригінальна відповідь була написана, щоб продемонструвати потужний комбінатор, mapReduceякий дозволяє мислити про нашу трансформацію по-іншому

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

Інтуїтивно mapReduceстворює новий редуктор, до якого можна підключити безпосередньо Array.prototype.reduce. Але що ще важливіше, ми можемо реалізувати реалізацію нашого об'єкторного функтора omapпросто, використовуючи моноїд об'єкта 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, впливаючи на значення, з якими пов'язані окремі клавіші (я фактично випадково повернув масиви, але виправив його). Поки не буде повернення в нормальному сенсі, не буде створений масив з оригінальним об'єктом стилю неушкодженим і модифікований як запрограмовано.

Ця конкретна програма приймає об'єкт, який називається зображенням, і приймає значення його ключів і додає теги 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') 
};

Початкова структура об'єкта залишається недоторканою, що забезпечує нормальний доступ до власності, доки не буде повернення. НЕ потрібно, щоб він повертав масив, як звичайний, і все буде добре. Мета - ПРИЗНАЧЕННЯ вихідних значень (зображень [клавіша]) потрібному, а не іншому. Наскільки я знаю, для того, щоб запобігти виведенню масиву, МОЖЕ бути ПОВЕРХНЕННЯ зображень [key] і жодних неявних чи явних запитів повернути масив (змінне призначення робить це і мені глючить туди-сюди).

Редагувати:

Зверніться до свого іншого методу щодо створення нового об'єкта, щоб уникнути зміни оригінального об'єкта (і перепризначення, як видається, все ще необхідно, щоб уникнути випадкового створення масиву як виводу). Ці функції використовують синтаксис стрілок і є, якщо ви просто хочете створити новий об’єкт для подальшого використання.

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) => значення) і просто повертає все, що зберігається там, як значення для цього ключа (або помножене на два, якщо ви зміните значення повернення, як він) у mapFn ( obj) [ключ],

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

і повертає операцію, виконану за результатом (акумулятор, розташований у дужках, ініційований в кінці функції .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"}

Майте на увазі, що це працювало з NON-NUMBERS. ВИ МОЖЕТЕ дублювати будь-який об’єкт, ПРОСТИЙ ПОВЕРНЕННЯ ЗНАЧЕННЯ у функції 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 Я додав другий метод, який створює те, що, можливо, карта. Це повільно, але робить те, що вимагає ОП Перший метод ідеальний у багатьох ситуаціях, саме тому я використовував функції циклу та властивостей.
Віктор Стоддард

1

Якщо вас цікавить mapping не лише значень, а й ключів, я написав 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);
}

фактObject =

{
"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 (функція (значення, мітка) {
    повернене значення * значення;
});
// 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

Макет об’єктів у 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.