funzione mappa per oggetti (anziché matrici)


1068

Ho un oggetto:

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

Sto cercando un metodo nativo, simile a Array.prototype.mapquello che sarebbe usato come segue:

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

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

JavaScript ha una tale mapfunzione per gli oggetti? (Lo voglio per Node.JS, quindi non mi importa dei problemi tra browser.)


1
La maggior parte delle risposte utilizza Object.keys, che non ha alcun ordine ben definito. Questo può essere problematico, suggerisco di usare Object.getOwnPropertyNamesinvece.
Oriol

14
Incredibile per me che JS non ha previsto questo compito incredibilmente rudimentale.
Jchook,

3
@Oriol ne sei sicuro? Secondo i documenti web MDN, l'ordinamento degli elementi dell'array è coerente tra Object.keyse Object.getOwnPropertyNames. Vedi developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Bart

@Bart L'ordine di Object.keysdipende dall'implementazione. Vedi stackoverflow.com/a/30919039/1529630
Oriol

4
@Oriol Non dovresti comunque fare affidamento sull'ordine delle chiavi negli oggetti, quindi è un po 'irrilevante per la domanda - dal momento che non ha mai specificato che l'ordine gli importava. E se l'ordine gli importasse, non dovrebbe comunque usare un oggetto.
BT,

Risposte:


1546

Non esiste un nativo mapper l' Objectoggetto, ma che ne dici di questo:

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 }

Ma puoi facilmente scorrere su un oggetto usando 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 }

Aggiornare

Molte persone affermano che i metodi precedenti non restituiscono un nuovo oggetto, ma piuttosto operano sull'oggetto stesso. Del resto, volevo aggiungere un'altra soluzione che restituisce un nuovo oggetto e lascia l'oggetto originale così com'è:

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.reduceriduce un array a un singolo valore unendo in qualche modo il valore precedente con quello corrente. La catena è inizializzata da un oggetto vuoto {}. Ad ogni iterazione myObjectviene aggiunta una nuova chiave di con il doppio della chiave come valore.

Aggiornare

Con le nuove funzionalità ES6, esiste un modo più elegante per esprimere 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
Sì, non consiglierei la mia soluzione per quel tipo di problema. Ecco perché ho aggiornato la mia risposta con una soluzione alternativa e migliore.
Lampade ad incandescenza l'

1
Ho aggiornato la mia risposta - sembra (rileggendo la domanda) che l'OP voglia un nuovo oggetto con le stesse chiavi, dove i valori sono stati mutati con la funzione fornita e non modificare l'oggetto originale in atto.
Alnitak,

13
@KhalilRavanna Penso che tu abbia letto male il codice qui - questa risposta non sta usando mapcorrettamente perché non sta facendo un return- sta abusando mapcome se fosse una forEachchiamata. Se lo facesse effettivamente ,return myObject[value] * 2 il risultato sarebbe una matrice contenente i valori originali raddoppiati, invece di un oggetto contenente le chiavi originali con valori raddoppiati, che è chiaramente ciò che l'OP ha richiesto.
Alnitak,

3
@Amberlamps il tuo .reduceesempio va bene, ma IMHO è ancora molto inferiore alla comodità di a .mapfor Objects, poiché il tuo .reducecallback non deve solo corrispondere al modo in cui .reducefunziona, ma richiede anche che myObjectsia disponibile nell'ambito lessicale. Quest'ultimo, in particolare, rende impossibile solo passare un riferimento di funzione nel callback, richiedendo invece una funzione anonima sul posto.
Alnitak,

8
TBH Avrei preferito annullare una risposta che solo (o originariamente) forniva la seconda soluzione presentata in questa risposta. Il primo funziona e non è necessariamente sbagliato, ma come altri hanno affermato, non mi piace vedere la mappa utilizzata in questo modo. Quando vedo la mappa, la mia mente pensa automaticamente "struttura dei dati immutabile"
mjohnsonengr,

308

Che ne dici di un liner con assegnazione variabile immediata in JS semplice ( ES6 / ES2015 )?

Utilizzando l' operatore spread e la sintassi del nome chiave calcolata :

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

jsbin

Un'altra versione che utilizza riduce:

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

jsbin

Primo esempio come funzione:

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

Se desideri mappare ricorsivamente un oggetto nidificato in uno stile funzionale , puoi farlo in questo modo:

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

O più imperativamente, in questo modo:

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

Dal momento che ES7 / ES2016 è possibile utilizzare Object.entries()al posto di Object.keys()esempio in questo modo:

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

Introduzione di ES2019Object.fromEntries() , che semplifica ulteriormente questo aspetto :

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


Proprietà ereditate e catena di prototipi:

In alcune rare situazioni potrebbe essere necessario mappare un oggetto simile a una classe che detiene le proprietà di un oggetto ereditato sulla sua catena di prototipi . In questi casi Object.keys()non funzionerà, perché Object.keys()non enumera le proprietà ereditate . Se è necessario mappare le proprietà ereditate , è necessario utilizzare for (key in myObj) {...}.

Ecco un esempio di un oggetto che eredita le proprietà di un altro oggetto e come Object.keys()non funziona in tale scenario.

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

Tuttavia, ti prego di farmi un favore ed evitare l' eredità . :-)


1
Bello ma sono stato catturato dal fatto che Object.keysnon enumera le proprietà ereditate. Ti suggerisco di aggiungere un avviso.
David Braun,

Per abbreviare la risposta, basta usare Object.entries({a: 1, b: 2, c: 3}).

1
Hai alcuni errori di battitura (ti riferisci a (o, f)come argomenti ma li usi objnel corpo.
kzahel,

4
Sì e poi lasciare che la persona successiva passi qualche minuto a capire cosa fa questa funzione? :)
Andrey Popov,

1
La soluzione Object.assign(...Object.entries(obj).map(([k, v]) => ({[k]: v * v})))non funziona se objè un oggetto vuoto. Cambia a: Object.assign({}, ...Object.entries(obj).map(([k, v]) => ({[k]: v * v}))).
blackcatweb,

117

Nessun metodo nativo, ma lodash # mapValues farà il lavoro brillantemente

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

6
Non è necessario aggiungere una chiamata HTTP aggiuntiva e una libreria aggiuntiva solo per una funzione. In ogni caso, questa risposta è obsoleta e puoi semplicemente chiamare Object.entries({a: 1, b: 2, c: 3})per ottenere un array.

11
Perché è utile ottenere un array? Il requisito era per un oggetto mappato. Inoltre, non c'è alcuna chiamata HTTP implicita nell'utilizzo di Lodash, una libreria popolare essenzialmente costruita per questo tipo di cose, anche se ci sono alcune cose che ES6 ha precluso la necessità di utilizzare le librerie di funzioni di utilità in qualche modo ultimamente.
corse32

2
suppongo che una chiamata HTTP aggiuntiva per tirare Lodash. Se questo è il tuo unico caso d'uso, anzi è eccessivo. Tuttavia, ha senso avere questa risposta perché Lodash è una biblioteca così diffusa.
igorsantos07

2
per non parlare del fatto che dovresti effettivamente utilizzarlo in _.map()modo da ottenere la chiave come secondo argomento, come richiesto dal PO.
igorsantos07

_.mapValues()fornisce anche la chiave come secondo argomento. Inoltre _.map()non funzionerebbe qui in quanto restituisce solo array, non oggetti:_.map({ 'a': 1, 'b': 2, 'c': 3}, n => n * 3) // [3, 6, 9]
MeltedPenguin

57

È abbastanza facile scriverne uno:

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

con codice di esempio:

> 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: questa versione consente anche di (facoltativamente) impostare il thiscontesto per il callback, proprio come il Arraymetodo.

EDIT : modificato per rimuovere l'uso di Object.prototype, per garantire che non si scontrino con alcuna proprietà esistente denominata mapsull'oggetto.


3
Ok. :) Un commento su questa domanda mi ha portato qui. Farò +1 su questo dato che la risposta accettata è chiaramente un uso improprio map, ma devo dire che la modifica Object.prototypenon è adatta a me, anche se non è enumerabile.
JLRishe,

2
@JLRishe Grazie. IMHO in ES5 non c'è più alcun motivo per non modificare Object.prototypealtro che il rischio (teorico) di collisione con altri metodi. FWIW, non riesco a capire come l'altra risposta abbia ottenuto il maggior numero di voti che ha, è completamente sbagliato.
Alnitak,

7
@JLRishe sei un vecchio geezer ... vai con i tempi e modifica quel prototipo!
Nick Manning,

12
@NickManning Voi giovani sbattitori e il vostro comportamento irresponsabile. Vattene dal mio prato!
JLRishe

3
Questo già non funziona: o = {map: true, image: false}. Stai rischiando solo di scrivere o.map(fn)invece dimap(o,fn)
fregante

25

È possibile utilizzare Object.keyse quindi forEachsull'array di chiavi restituito:

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

O in modo più modulare:

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; });

Si noti che Object.keysrestituisce un array contenente solo le proprietà enumerabili dell'oggetto, quindi si comporta come un for..inciclo con un hasOwnPropertysegno di spunta.


20

Questo è dritto bs, e tutti nella comunità JS lo sanno. Ci dovrebbe essere questa funzionalità:

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}

ecco l'implementazione ingenua:

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

è super fastidioso doverlo implementare da soli tutto il tempo;)

Se vuoi qualcosa di un po 'più sofisticato, che non interferisca con la classe Object, prova questo:

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;
});

ma è sicuro aggiungere questa funzione mappa a Object, semplicemente non aggiungere a Object.prototype.

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

Perché aggiungi questa funzione Objectall'oggetto globale ? Basta avere const mapObject = (obj, fn) => { [...]; return ret; }.
Lampade ad ambra

7
Perché dovrebbe essere un metodo su Object :)
Alexander Mills,

1
Finché non si scherza con Object.prototype, allora dovremmo essere ok
Alexander Mills,

1
Se la mappa fosse solo una cosa vaga casuale, certo. Ma .mapè generalmente inteso come un'interfaccia di Functor e non esiste un'unica definizione di Functor per "Object" perché praticamente qualsiasi cosa in javascript può essere un Object, comprese le cose con le proprie interfacce / logiche .map molto distinte e definite.
Dtipson,

1
A mio avviso, il primo argomento di callback dovrebbe essere l'elemento iterato stesso, non una chiave. Ad esempio suppongo che dovrebbe darmi lo stesso oggetto, come al solito accade con map comune: Object.map ({prop: 1}, el => el) ma restituisce chiavi con valore con lo stesso nome chiave ...
Gleb Dolzikov

17

Sono venuto qui cercando di trovare e rispondere per mappare un oggetto su un array e ho ottenuto questa pagina come risultato. Nel caso in cui venissi qui alla ricerca della stessa risposta, ecco come puoi mappare e obiettare a un array.

È possibile utilizzare map per restituire un nuovo array dall'oggetto in questo modo:

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

6
Questo non funziona in remoto: restituisce solo una matrice dei valori dell'oggetto, non un nuovo oggetto. Non posso credere che abbia così tanti voti positivi.
Alnitak,

1
Hai ragione, non risponde correttamente alla domanda poiché stavano cercando una risposta per mappare un oggetto e un oggetto. Sono venuto qui alla ricerca di una risposta per mappare un oggetto su un array e ho ottenuto questa pagina come risultato, quindi lascerò la mia risposta e la modificherò per riflettere che è una risposta alternativa per le persone che potrebbero essere arrivate qui a causa di cercando di mappare un oggetto su array.
JoeTron

5
In ES7 sarà banalmenteObject.values(myObject)
Alnitak il

1
È bello sapere, ma alcune persone devono ancora usare il codice legacy.
JoeTron

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

12

La risposta accettata presenta due inconvenienti:

  • Abusa Array.prototype.reduce, perché ridurre significa cambiare la struttura di un tipo composito, cosa che in questo caso non accade.
  • Non è particolarmente riutilizzabile

Un approccio funzionale ES6 / ES2015

Si noti che tutte le funzioni sono definite in forma curry.

// 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));

  • Nella riga A oviene riassegnato. Poiché Javascript passa valori di riferimento mediante condivisione , oviene generata una copia superficiale di . Ora siamo in grado di mutare oall'interno omapsenza mutare onell'ambito genitore.
  • Nella riga B mapil valore di ritorno viene ignorato, poiché mapesegue una mutazione di o. Poiché questo effetto collaterale rimane all'interno omape non è visibile nell'ambito genitore, è totalmente accettabile.

Questa non è la soluzione più veloce, ma dichiarativa e riutilizzabile. Ecco la stessa implementazione di una riga, succinta ma meno leggibile:

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

Addendum: perché gli oggetti non sono iterabili per impostazione predefinita?

ES2015 ha specificato l'iteratore e i protocolli iterabili. Ma gli oggetti non sono ancora iterabili e quindi non mappabili. Il motivo è la combinazione di dati e livello di programma .


1
Sembra un po 'troppo ingegnerizzato; Conto 6 funzioni per quello che è essenzialmente un ciclo. Altre soluzioni sono molto più semplici e anche la risposta accettata è 5 volte più veloce.
fregante,

4
@ bfred.it Qual è lo scopo del tuo commento? Se ti piace la micro-ottimizzazione, non dovresti nemmeno usare Array.prototype.reduce. Tutto quello che voglio illustrare qui è che la risposta accettata in qualche modo "abusa" Array.prototype.reducee come potrebbe essere implementata una mappatura puramente funzionale. Se non sei interessato alla programmazione funzionale, ignora la mia risposta.

2
"ridurre significa cambiare la struttura di un tipo composito" Non credo sia vero. Un Array.reduce che produce un nuovo array della stessa identica lunghezza o anche degli stessi stessi valori è perfettamente legittimo. Riduzione può essere utilizzata per ricreare la funzionalità di mappa e / o filtro (o entrambi, come in un trasduttore complesso, per cui la riduzione è la base). In FP, è un tipo di piega e una piega che finisce con lo stesso tipo è ancora una piega.
Dtipson,

2
@Dtipson Hai assolutamente ragione. A foldè più generico di un map(functor). Tuttavia, questa era la mia conoscenza da metà 2016. Questa è mezza eternità: D

1
Ah sì Mi ci sono voluti anni per capire quanto fossero interconnesse tutte queste cose e il più ampio ecosistema di termini e interfacce. Le cose che sembrano essere così facili si rivelano incredibilmente complesse e alcune cose che sembrano astrattemente complesse si sottraggono abbastanza ordinatamente. :)
Dtipson,

10

JavaScript ha appena ottenuto il nuovo Object.fromEntriesmetodo.

Esempio

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)

Spiegazione

Il codice sopra converte l'oggetto in una matrice nidificata ( [[<key>,<value>], ...]) su cui è possibile mappare. Object.fromEntriesconverte l'array in un oggetto.

La cosa interessante di questo modello è che ora puoi facilmente prendere in considerazione le chiavi degli oggetti durante la mappatura.

Documentazione

Supporto per il browser

Object.fromEntriesè attualmente supportato solo da questi browser / motori , tuttavia sono disponibili polyfill (ad es. @ babel / polyfill ).


2
Fondamentalmente, Object.fromEntriesè l'opposto di Object.entries. Object.entriesconverte un oggetto in un elenco di coppie chiave-valore. Object.fromEntriesconverte un elenco di coppie chiave-valore in un oggetto.
orad,

8

Versione minima (es6):

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

Penso che questo non sia corretto a causa di un errore di battitura, penso che tu intenda => Object.entries(obj).reduce((a, [k, v]) => [a[k] = v * v, a], {}), parentesi angolate anziché parentesi.
Alexander Mills

@AlexanderMills, il mio codice è corretto. Maggiori informazioni
sull'operatore

capito, potresti volerlo spiegare la prossima volta, ma immagino che sia a questo che servono i commenti.
Alexander Mills,

1
sostanzialmente, il mio codice equivale a:Object.entries(obj).reduce((a, [k, v]) => {a[k] = v * v; return a}, {})
Yukulélé,

1
Bella risposta. Object.entries().reduceè la soluzione migliore Penso che con ES6 + .Includere ottenere più upvotes se ha usato un returncomunicato nel riduttore, piuttosto che implicit returne comma'operatore - che è ordinata, ma difficile da leggere IMHO
Drenai

7

Per le massime prestazioni.

Se il tuo oggetto non cambia spesso ma deve essere ripetuto spesso, ti suggerisco di utilizzare una mappa nativa come cache.

// 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 funziona già in Chrome, Edge, Firefox e beta Opera, quindi è una funzionalità a prova di futuro. Proviene da ES7, quindi effettua il polyfill https://github.com/es-shims/Object.entries per IE dove non funziona.


Che ne dici di usare Destructing ?
Константин Ван,

1
@ K._ era molto lento, ma non conosco le prestazioni ora. Sembra che V8 v 5.6 abbia introdotto alcune ottimizzazioni per la destrutturazione ma che devono essere misurate v8project.blogspot.co.uk
Pawel

Oppure, es7 meno performante ma immutabile:const fn = v => v * 2; const newObj = Object.entries(myObject).reduce((acc, [k,v]) => Object.assign({}, acc, {[k]: fn(v)}), {});
mikebridge

Mi chiedo perché nessuna delle altre risposte usi Object.entries. È molto più pulito dell'iterare sui tasti e usare obj [tasto].
corwin.amber,

6

puoi usare il mapmetodo e forEachsulle matrici ma se vuoi usarlo su Objectallora puoi usarlo con twist come di seguito:

Utilizzo di 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}

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

Oppure puoi usare altri loop come il $.eachmetodo come nell'esempio seguente:

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

Dov'è $jquery?
masterxilo,

Sì, puoi usare sia $ che jQuery
Haritsinh Gohil il

1
@ Ronald ho frainteso, vedi la mia risposta aggiornata, grazie per non aver effettuato il downvoting ma per aver reso veramente conto del mio errore, grazie per aver migliorato la community.
Haritsinh Gohil,

@bcoughlan sì, è esattamente quello che ho scritto anche nei commenti, sta quadrando i numeri, cosa vuoi allora?
Haritsinh Gohil,

1
@HaritsinhGohil Ho pensato che ti stesse criticando per aver dato una soluzione jQuery. È vero che in questi giorni c'è un po 'di slancio per allontanarsi da jQuery, e ovviamente c'è Node, ma a meno che un OP non richieda specificamente JS o Node puri, penso che vada bene supporre che qualcuno stia lavorando in un ambiente web e che jQuery è quasi certo di essere disponibile.
Abalter,

4

Il map functionnon esiste sul Object.prototypetuttavia è possibile emularlo in questo modo

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;
});

Problemi di prestazione: typeof callback === 'function'assolutamente ridondanti. 1) mapnon ha alcun significato senza callback 2) se callbacknon funziona, la tua mapimplementazione viene eseguita senza for-loop. Inoltre, Object.prototype.hasOwnProperty.call( obj, key )è un po 'eccessivo.
ankhzet,

3

Ho trovato questo come primo elemento in una ricerca di Google cercando di imparare a farlo, e ho pensato di condividere con altri folsk trovando di recente la soluzione che ho trovato, che utilizza il pacchetto npm immutabile.

Penso che sia interessante condividere perché immutabile utilizza la situazione EXACT dell'OP nella propria documentazione - il seguente non è il mio codice, ma tratto dalla documentazione attuale di 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 } 

Non che Seq abbia altre proprietà ("Seq descrive un'operazione pigra, consentendo loro di concatenare in modo efficiente l'uso di tutti i metodi di raccolta di ordine superiore (come mappa e filtro) non creando raccolte intermedie") e che alcuni altri dati immutabili-js le strutture potrebbero anche svolgere il lavoro in modo abbastanza efficiente.

Chiunque utilizzi questo metodo dovrà ovviamente npm install immutablee potrebbe voler leggere i documenti:

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


3

EDIT: il modo canonico utilizzando le nuove funzionalità JavaScript è -

const identity = x =>
  x

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

Dov'è oqualche oggetto ed fè la tua funzione di mappatura. Oppure potremmo dire, data una funzione da a -> be un oggetto con valori di tipo a, produrre un oggetto con valori di tipo b. Come firma di tipo pseudo -

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

La risposta originale è stata scritta per dimostrare un potente combinatore, mapReduceche ci consente di pensare alla nostra trasformazione in modo diverso

  1. m, la funzione di mappatura : ti dà la possibilità di trasformare l'elemento in arrivo prima di ...
  2. r, la funzione di riduzione : questa funzione combina l'accumulatore con il risultato dell'elemento mappato

Intuitivamente, mapReducecrea un nuovo riduttore che possiamo collegare direttamente Array.prototype.reduce. Ma, cosa ancora più importante, possiamo implementare omapchiaramente la nostra implementazione del funzione oggetto utilizzando il monoid oggetto Object.assigne {}.

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 }

Nota che l'unica parte del programma che abbiamo dovuto scrivere è l'implementazione della mappatura stessa -

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

Che dice, dato un oggetto conosciuto oe qualche chiave k, costruire un oggetto e la cui proprietà calcolato kè il risultato della chiamata fsul valore della chiave, o[k].

Vediamo il mapReducepotenziale del sequenziamento se in primo luogo astraggiamooreduce

// 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
    )

Tutto funziona allo stesso modo, ma ora omappuò essere definito a un livello superiore. Naturalmente il nuovo Object.entriesrende questo aspetto sciocco, ma l'esercizio è ancora importante per lo studente.

Non vedrai il pieno potenziale di mapReducequi, ma condivido questa risposta perché è interessante vedere quanti posti può essere applicato. Se sei interessato a come viene derivato e ad altri modi in cui potrebbe essere utile, consulta questa risposta .


4
Usiamo piuttosto Mape Map.entries, OK: D. Sono stanco di abusare di oggetti semplici come tipi di dati della mappa.

2
Sono d'accordo che Mapdovrebbe essere usato dove / quando possibile, ma non sostituisce totalmente la necessità di usare queste procedure su semplici oggetti JS anche se: D
Grazie

2

Basato sulla risposta di @Amberlamps, ecco una funzione di utilità (come commento sembrava brutto)

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

e l'uso è:

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 }

enumerableil valore predefinito èfalse
Grazie

2

La mia risposta è in gran parte basata sulla risposta più votata qui e spero che tutti capiscano (hanno la stessa spiegazione anche sul mio GitHub). Ecco perché la sua impementazione con la mappa funziona:

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

Lo scopo della funzione è di prendere un oggetto e modificare il contenuto originale dell'oggetto usando un metodo disponibile per tutti gli oggetti (oggetti e matrici allo stesso modo) senza restituire un array. Quasi tutto in JS è un oggetto, e per questo motivo elementi più in basso nella pipeline dell'eredità possono potenzialmente tecnicamente utilizzare quelli disponibili per quelli in cima alla linea (e al contrario appare).

Il motivo per cui funziona è dovuto alle funzioni .map che restituiscono un array RICHIESTO di fornire un RITORNO esplicito o implicito di un array anziché semplicemente modificare un oggetto esistente. In sostanza, induci il programma a pensare che l'oggetto sia un array usando Object.keys che ti permetterà di usare la funzione map con la sua azione sui valori a cui sono associate le singole chiavi (in realtà ho restituito accidentalmente array ma lo ho riparato). Finché non c'è un ritorno in senso normale, non ci sarà alcun array creato con l'oggetto originale intatto e modificato come programmato.

Questo particolare programma prende un oggetto chiamato images e prende i valori delle sue chiavi e aggiunge i tag url per l'uso all'interno di un'altra funzione. L'originale è questo:

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

... e modificato è questo:

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

La struttura originale dell'oggetto viene lasciata intatta, consentendo il normale accesso alle proprietà purché non vi sia un ritorno. NON farlo restituire un array come normale e tutto andrà bene. L'obiettivo è Riassegnare i valori originali (immagini [chiave]) a ciò che si desidera e non a nient'altro. Per quanto ne so, al fine di prevenire l'output dell'array, DEVE essere RIABILITAZIONE delle immagini [chiave] e nessuna richiesta implicita o esplicita di restituire un array (l'assegnazione variabile fa questo e stava andando avanti e indietro per me).

MODIFICARE:

Andando ad affrontare il suo altro metodo per quanto riguarda la creazione di nuovi oggetti per evitare di modificare l'oggetto originale (e la riassegnazione sembra essere ancora necessaria per evitare di creare accidentalmente un array come output). Queste funzioni utilizzano la sintassi della freccia e sono se si desidera semplicemente creare un nuovo oggetto per uso futuro.

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

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

Il modo in cui funzionano queste funzioni è così:

mapFn accetta la funzione da aggiungere in seguito (in questo caso (valore) => valore) e restituisce semplicemente tutto ciò che è memorizzato lì come valore per quella chiave (o moltiplicato per due se si modifica il valore restituito come ha fatto lui) in mapFn ( obj) [key],

e quindi ridefinisce il valore originale associato alla chiave nel risultato [chiave] = mapFn (obj) [chiave]

e restituisce l'operazione eseguita sul risultato (l'accumulatore situato tra parentesi iniziate al termine della funzione .riduce).

Tutto questo viene eseguito sull'oggetto scelto e ANCORA NON PUO 'ESSERE una richiesta implicita per un array restituito e funziona solo quando si riassegnano valori per quanto posso dire. Ciò richiede una certa ginnastica mentale, ma riduce le linee di codice necessarie come si può vedere sopra. L'output è esattamente lo stesso che si può vedere di seguito:

{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"}

Tieni presente che ha funzionato con i NON NUMERI. È possibile duplicare QUALUNQUE oggetto SEMPLICEMENTE RITORNANDO IL VALORE nella funzione mapFN.


2

La prima funzione risponde alla domanda. Crea una mappa.

La seconda funzione non crea una mappa. Al contrario, scorre attraverso le proprietà nell'oggetto esistente usando hasOwnProperty(). Questa alternativa alla mappa può essere una soluzione migliore in alcune situazioni.

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
Questo non risponde alla domanda originale - non sta mappando i valori su un nuovo oggetto con una funzione fornita (cioè questo non assomiglia a Array.prototype.map).
Matt Browne,

@MattBrowne Ho aggiunto un secondo metodo che crea quella che è probabilmente una mappa. È lento, ma fa ciò che l'OP richiede. Il primo metodo è ideale in molte situazioni, motivo per cui ho usato un loop e funzioni di proprietà.
Victor Stoddard,

1

Se sei interessato al mapping non solo ai valori ma anche alle chiavi, ho scritto Object.map(valueMapper, keyMapper), che si comporta in questo modo:

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
Presumo perché in sostanza hai appena fornito un link come risposta, che è mal visto.
Chris Wright,

Se mai utile per chiunque: npm install @mattisg/object.map.
MattiSG,

1

Avevo bisogno di una versione che permettesse di modificare anche i tasti (basato sulle risposte di @Amberlamps e @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"}
}

Modifica: leggera modifica da passare nell'oggetto iniziale {}. Permette che sia [] (se le chiavi sono numeri interi)


1

Volevo specificamente usare la stessa funzione che stavo usando per gli array per un singolo oggetto e volevo mantenerlo semplice. Questo ha funzionato per me:

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

questo non è diverso davar mapped = myMapFunction(item)
Grazie

Questo è molto semplice rispetto a tutte le altre soluzioni di "scienza missilistica" che ho visto qui. Grazie per aver condiviso :)
Yuri Teixeira il

1

Per rispondere più da vicino a ciò che esattamente l'OP ha richiesto, l'OP vuole un oggetto:

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

avere un metodo cartografico myObject.map,

simile a Array.prototype.map che verrebbe utilizzato come segue:

newObject = myObject.map (funzione (valore, etichetta) {
    valore restituito * valore;
});
// newObject ora è {'a': 1, 'b': 4, 'c': 9}

La migliore risposta imho (misurata in termini di " vicino a ciò che viene chiesto " + "nessuna ES {5,6,7} richiesta inutilmente") sarebbe:

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

Il codice sopra evita di usare intenzionalmente qualsiasi funzionalità linguistica, disponibile solo nelle recenti edizioni ECMAScript. Con il codice sopra il problema può essere risolto come questo:

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);
codice di prova alternativo qui

Oltre ad essere disapprovato da alcuni, sarebbe possibile inserire la soluzione nella catena di prototipi in questo modo.

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

Qualcosa che, se fatto con un'attenta supervisione, non dovrebbe avere effetti negativi e non influire sul mapmetodo di altri oggetti (ad es. Quello di Array map).



1

Definire una funzione mapEntries.

mapEntries accetta una funzione di callback che viene chiamata su ogni voce nell'oggetto con i parametri valore, chiave e oggetto. Dovrebbe restituire un nuovo valore.

mapEntries dovrebbe restituire un nuovo oggetto con i nuovi valori restituiti dal callback.

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}

Modifica: una versione precedente non specificava che questa non fosse una proprietà enumerabile


0

Hey ha scritto una piccola funzione mapper che potrebbe essere d'aiuto.

    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
Immagino volessi dire "io" invece di "Ehi"
Carlo

0

Un approccio diverso è quello di utilizzare una funzione personalizzata di json stringify che può funzionare anche su oggetti profondi. Questo potrebbe essere utile se intendi pubblicarlo sul server comunque come 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

Gestisco solo stringhe per ridurre le esenzioni:

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

0

Mappatore oggetti in TypeScript

Mi piacciono gli esempi che usano Object.fromEntriescome questo , ma comunque non sono molto facili da usare. Le risposte che usano Object.keyse poi cercano keysono in realtà più ricerche che potrebbero non essere necessarie.

Avrei voluto che ci fosse una Object.mapfunzione, ma possiamo crearne una nostra e chiamarla objectMapcon la possibilità di modificare entrambe keye value:

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

Codice (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;
}

Se non si utilizza TypeScript, copiare il codice sopra in Playground TypeScript per ottenere il codice JavaScript.

Inoltre, il motivo che ho inserito keySelectordopo valSelectornell'elenco dei parametri è perché è facoltativo.

* Un po 'di merito va alla risposta di alexander-mills .


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

Questa funzione ricorre in modo ricorsivo all'oggetto e alle matrici di oggetti. Gli attributi possono essere eliminati se restituiti indefiniti

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.