Z tablicy obiektów wyodrębnij wartość właściwości jako tablicy


1016

Mam tablicę obiektów JavaScript o następującej strukturze:

objArray = [ { foo: 1, bar: 2}, { foo: 3, bar: 4}, { foo: 5, bar: 6} ];

Chcę wyodrębnić pole z każdego obiektu i uzyskać tablicę zawierającą wartości, na przykład pole foodałoby tablicę [ 1, 3, 5 ].

Mogę to zrobić z tym trywialnym podejściem:

function getFields(input, field) {
    var output = [];
    for (var i=0; i < input.length ; ++i)
        output.push(input[i][field]);
    return output;
}

var result = getFields(objArray, "foo"); // returns [ 1, 3, 5 ]

Czy istnieje bardziej elegancki lub idiomatyczny sposób, aby to zrobić, aby niestandardowa funkcja narzędzia była niepotrzebna?


Uwaga na temat sugerowanego duplikatu , dotyczy to sposobu konwersji pojedynczego obiektu na tablicę.


3
Biblioteka Prototypów dodała funkcję „zbierania” do prototypu Array (tak myślę), abyś mógł pisać var foos = objArray.pluck("foo");.
Pointy,

3
@hyde - jsperf.com/map-vs-native-for-loop - proszę spojrzeć na to, mam nadzieję, że samo zapętlenie
zapisze

1
@ N20084753 w celu przeprowadzenia rzetelnego testu należy również porównać natywną Array.prototype.mapfunkcję tam, gdzie ona istnieje
Alnitak

@Alnitak - masz rację, ale nie rozumiem, w jaki sposób natywna Array.prototype.map jest zoptymalizowana niż natywna dla pętli, w jakikolwiek sposób musimy przejść przez całą tablicę.
N20084753,

3
OP, wolę twoje podejście od wszelkich innych, które zostały zasugerowane. Nie ma w tym nic złego.

Odpowiedzi:


1334

Oto krótszy sposób na osiągnięcie tego:

let result = objArray.map(a => a.foo);

LUB

let result = objArray.map(({ foo }) => foo)

Możesz także sprawdzić Array.prototype.map().


3
Cóż, jest to to samo, co komentarz totymedli z innej odpowiedzi, ale mimo wszystko jest to (moim zdaniem) lepszy sposób niż w innych odpowiedziach , więc ... Zmieniam to na akceptowaną odpowiedź.
hyde

4
@PauloRoberto Funkcje strzałek są zasadniczo obsługiwane wszędzie oprócz IE.
tgies

1
jasne, jest to dozwolone, ale IMHO nie ma nic, co obiektywnie poprawia tę odpowiedź, z wyjątkiem tego, że używa składni, która nie była dostępna w momencie zadawania pytania i nie jest nawet obsługiwana w niektórych przeglądarkach. Chciałbym również zauważyć, że ta odpowiedź jest bezpośrednią kopią komentarzy, które zostały wprowadzone do pierwotnie przyjętej odpowiedzi prawie rok przed opublikowaniem tej odpowiedzi.
Alnitak,

26
@Alnitak Korzystanie nowsze funkcje, w mojego punktu widzenia, jest obiektywnie lepszy. Ten fragment jest niezwykle powszechny, więc nie jestem przekonany, czy to plagiat. Nie ma żadnej wartości w utrzymywaniu nieaktualnych odpowiedzi na górze.
Rob

6
komentarze nie są odpowiedziami, imo jeśli ktoś opublikuje komentarz zamiast odpowiedzi, to jego wina, jeśli ktoś skopiuje go do odpowiedzi.
Aequitas

618

Tak, ale opiera się na funkcji JavaScript JavaScript ES5. Oznacza to, że nie będzie działać w IE8 lub starszej wersji.

var result = objArray.map(function(a) {return a.foo;});

W interpretatorach JS kompatybilnych z ES6 możesz użyć funkcji strzałki dla zwięzłości:

var result = objArray.map(a => a.foo);

Dokumentacja Array.prototype.map


5
To rozwiązanie wydaje się być schludne i proste, ale jest zoptymalizowane niż metoda zwykłego zapętlania.
N20084753,

czy OP nie chce metody, aby uzyskać jakieś pole, a nie tylko zakodowane na stałe foo?
Alnitak

2
w ES6 możesz zrobićvar result = objArray.map((a) => (a.foo));
Czarny

28
@Black Jeszcze lepiej:var result = objArray.map(a => a.foo);
totymedli

4
@FaizKhan Zwróć uwagę na rok. Ta odpowiedź została opublikowana 25 października ... 2013 r. Odpowiedź „zaakceptowana” została opublikowana 11 października 2017 r. Jeśli jest to niezwykłe, znacznik trafił do drugiej.
Niet the Dark Absol

52

Sprawdź funkcję Lodasha_.pluck() lub funkcję podkreślenia_.pluck() . Oba robią dokładnie to, co chcesz w jednym wywołaniu funkcji!

var result = _.pluck(objArray, 'foo');

Aktualizacja: _.pluck()została usunięta od Lodash v4.0.0 , na korzyść _.map()w połączeniu z czymś podobnym do odpowiedzi Nieta . _.pluck()jest nadal dostępny w podkreśleniu .

Aktualizacja 2: Jak Mark wskazuje w komentarzach , gdzieś pomiędzy Lodash v4 a 4.3, dodano nową funkcję, która zapewnia tę funkcjonalność ponownie. _.property()jest stenograficzną funkcją, która zwraca funkcję do pobierania wartości właściwości w obiekcie.

Dodatkowo _.map()umożliwia teraz przekazanie ciągu jako drugiego parametru, do którego jest przekazywany _.property(). W rezultacie następujące dwa wiersze są równoważne powyższemu przykładowi kodu z wersji sprzed Lodash 4.

var result = _.map(objArray, 'foo');
var result = _.map(objArray, _.property('foo'));

_.property(), a zatem _.map()pozwalają również podać ciąg lub tablicę oddzieloną kropkami, aby uzyskać dostęp do właściwości podrzędnych:

var objArray = [
    {
        someProperty: { aNumber: 5 }
    },
    {
        someProperty: { aNumber: 2 }
    },
    {
        someProperty: { aNumber: 9 }
    }
];
var result = _.map(objArray, _.property('someProperty.aNumber'));
var result = _.map(objArray, _.property(['someProperty', 'aNumber']));

Oba _.map()połączenia w powyższym przykładzie zostaną zwrócone [5, 2, 9].

Jeśli bardziej interesujesz się programowaniem funkcjonalnym, spójrz na funkcję Ramdy R.pluck() , która wyglądałaby mniej więcej tak:

var result = R.pluck('foo')(objArray);  // or just R.pluck('foo', objArray)

5
Dobra wiadomość: gdzieś pomiędzy Lodash 4.0.0 a 4.3.0 _.property('foo')( lodash.com/docs#property ) dodano jako skrót function(o) { return o.foo; }. Skraca to przypadek użycia Lodash. var result = _.pluck(objArray, _.property('foo'));Dla większej wygody _.map() metoda Lodash 4.3.0 pozwala również na stosowanie skrótu _.property()pod maską, w wyniku czegovar result = _.map(objArray, 'foo');
Mark A. Fitzgerald

45

Mówiąc o rozwiązaniach tylko dla JS, odkryłem, że choć nieelegancka, prosta forpętla indeksowana jest bardziej wydajna niż jej alternatywy.

Wyodrębnianie pojedynczej właściwości z tablicy elementów 100000 (przez jsPerf)

Tradycyjny dla pętli 368 operacji / sek

var vals=[];
for(var i=0;i<testArray.length;i++){
   vals.push(testArray[i].val);
}

ES6 dla ... pętli 303 operacji / s

var vals=[];
for(var item of testArray){
   vals.push(item.val); 
}

Array.prototype.map 19 operacji / s

var vals = testArray.map(function(a) {return a.val;});

TL; DR - .map () jest wolny, ale możesz go używać, jeśli uważasz, że czytelność jest warta więcej niż wydajność.

Edytuj # 2: 6/2019 - Uszkodzony, usunięty link jsPerf.


1
Nazywaj mnie jak chcesz, ale liczy się wydajność. Jeśli potrafisz zrozumieć mapę, możesz zrozumieć pętlę for.
illcrx

Chociaż przydatne jest poznanie wyników testów porównawczych, myślę, że tutaj jest niewłaściwe, ponieważ nie odpowiada na to pytanie. Aby to poprawić, albo dodaj prawdziwą odpowiedź, albo skompresuj ją w komentarz (jeśli to możliwe).
Ifedi Okonkwo

15

Używanie Array.prototype.map:

function getFields(input, field) {
    return input.map(function(o) {
        return o[field];
    });
}

Zobacz powyższy link, aby uzyskać podkładkę dla przeglądarek wcześniejszych niż ES5.


15

Lepiej jest użyć pewnego rodzaju bibliotek, takich jak lodash lub podkreślnik, dla zapewnienia bezpieczeństwa w różnych przeglądarkach.

W Lodash można uzyskać wartości właściwości w tablicy, wykonując następującą metodę

_.map(objArray,"foo")

oraz w podkreśleniu

_.pluck(objArray,"foo")

Oba powrócą

[1, 2, 3]

10

W ES6 możesz:

const objArray = [{foo: 1, bar: 2}, {foo: 3, bar: 4}, {foo: 5, bar: 6}]
objArray.map(({ foo }) => foo)

Co jeśli foo jest niezdefiniowany? Tablica niezdefiniowanych nie jest dobra.
godhar

6

Chociaż mapjest właściwym rozwiązaniem do wybierania „kolumn” z listy obiektów, ma jednak pewną wadę. Jeśli nie zostanie wyraźnie zaznaczone, czy kolumny istnieją, wygeneruje błąd i (co najwyżej) zapewni undefined. Wybrałbym reducerozwiązanie, które może po prostu zignorować właściwość lub nawet ustawić dla ciebie wartość domyślną.

function getFields(list, field) {
    //  reduce the provided list to an array only containing the requested field
    return list.reduce(function(carry, item) {
        //  check if the item is actually an object and does contain the field
        if (typeof item === 'object' && field in item) {
            carry.push(item[field]);
        }

        //  return the 'carry' (which is the list of matched field values)
        return carry;
    }, []);
}

przykład jsbin

Działa to nawet wtedy, gdy jeden z elementów na podanej liście nie jest obiektem lub nie zawiera pola.

Można go nawet uelastycznić, negocjując wartość domyślną, jeśli element nie jest obiektem lub nie zawiera pola.

function getFields(list, field, otherwise) {
    //  reduce the provided list to an array containing either the requested field or the alternative value
    return list.reduce(function(carry, item) {
        //  If item is an object and contains the field, add its value and the value of otherwise if not
        carry.push(typeof item === 'object' && field in item ? item[field] : otherwise);

        //  return the 'carry' (which is the list of matched field values)
        return carry;
    }, []);
}

przykład jsbin

Tak samo byłoby z mapą, ponieważ długość zwróconej tablicy byłaby taka sama jak dostarczonej tablicy. (W takim przypadku a mapjest nieco tańsze niż a reduce):

function getFields(list, field, otherwise) {
    //  map the provided list to an array containing either the requested field or the alternative value
    return list.map(function(item) {
        //  If item is an object and contains the field, add its value and the value of otherwise if not
        return typeof item === 'object' && field in item ? item[field] : otherwise;
    }, []);
}

przykład jsbin

A potem jest najbardziej elastyczne rozwiązanie, które pozwala przełączać się między oboma zachowaniami, po prostu zapewniając alternatywną wartość.

function getFields(list, field, otherwise) {
    //  determine once whether or not to use the 'otherwise'
    var alt = typeof otherwise !== 'undefined';

    //  reduce the provided list to an array only containing the requested field
    return list.reduce(function(carry, item) {
        //  If item is an object and contains the field, add its value and the value of 'otherwise' if it was provided
        if (typeof item === 'object' && field in item) {
            carry.push(item[field]);
        }
        else if (alt) {
            carry.push(otherwise);
        }

        //  return the 'carry' (which is the list of matched field values)
        return carry;
    }, []);
}

przykład jsbin

Ponieważ powyższe przykłady (mam nadzieję) rzuciły nieco światła na sposób, w jaki to działa, pozwólmy nieco skrócić tę funkcję, korzystając z tej Array.concatfunkcji.

function getFields(list, field, otherwise) {
    var alt = typeof otherwise !== 'undefined';

    return list.reduce(function(carry, item) {
        return carry.concat(typeof item === 'object' && field in item ? item[field] : (alt ? otherwise : []));
    }, []);
}

przykład jsbin


6

Ogólnie rzecz biorąc, jeśli chcesz ekstrapolować wartości obiektów znajdujące się w tablicy (jak opisano w pytaniu), możesz użyć funkcji redukcji, mapowania i destrukcji tablicy.

ES6

let a = [{ z: 'word', c: 'again', d: 'some' }, { u: '1', r: '2', i: '3' }];
let b = a.reduce((acc, obj) => [...acc, Object.values(obj).map(y => y)], []);

console.log(b)

Odpowiednikiem użycia pętli for byłoby:

for (let i in a) {
  let temp = [];
  for (let j in a[i]) {
    temp.push(a[i][j]);
  }
  array.push(temp);
}

Wyprodukowano: [„słowo”, „ponownie”, „niektóre”, „1”, „2”, „3”]


3

To zależy od twojej definicji „lepszego”.

Inne odpowiedzi wskazują na użycie mapy, która jest naturalna (szczególnie dla facetów przyzwyczajonych do funkcjonalnego stylu) i zwięzła. Zdecydowanie polecam go używać (jeśli nie przeszkadza ci kilku facetów IE8-IT). Więc jeśli „lepszy” oznacza „bardziej zwięzły”, „łatwy do utrzymania”, „zrozumiały”, to tak, jest o wiele lepszy.

Z drugiej strony to piękno nie przychodzi bez dodatkowych kosztów. Nie jestem wielkim fanem mikrobenchów, ale tutaj przeprowadziłem mały test . Rezultaty są przewidywalne, stary brzydki sposób wydaje się być szybszy niż funkcja mapy. Więc jeśli „lepszy” oznacza „szybszy”, to nie, pozostań przy modzie starej szkoły.

Znowu jest to tylko mikrobench i w żaden sposób nie odradzam używania map, to tylko moje dwa centy :).


Cóż, tak naprawdę jest to po stronie serwera, a nie w przeglądarce, więc IE8 nie ma znaczenia. Tak, mapto obiwna droga, jakoś nie udało mi się znaleźć google (znalazłem tylko mapę jquery, co nie jest istotne).
hyde

3

Jeśli chcesz również obsługiwać obiekty podobne do tablic, użyj Array.from (ES2015):

Array.from(arrayLike, x => x.foo);

Przewagą nad metodą Array.prototype.map () jest to, że dane wejściowe mogą być również zestawem :

let arrayLike = new Set([{foo: 1}, {foo: 2}, {foo: 3}]);

2

Jeśli chcesz mieć wiele wartości w ES6 +, następujące będą działać

objArray = [ { foo: 1, bar: 2, baz: 9}, { foo: 3, bar: 4, baz: 10}, { foo: 5, bar: 6, baz: 20} ];

let result = objArray.map(({ foo, baz }) => ({ foo, baz }))

Działa to tak, jak {foo, baz}po lewej stronie jest niszczenie obiektów, a po prawej stronie strzałki jest równoważne ze {foo: foo, baz: baz}względu na ulepszone literały obiektów ES6 .


właśnie tego potrzebowałem, jak myślisz, jak szybkie / wolne jest to rozwiązanie?
Ruben

1
@ Ruben Aby naprawdę powiedzieć, myślę, że musisz wziąć różne opcje z tej strony i przetestować je przy użyciu czegoś takiego jak jsperf.com
Chris Magnuson

1

Mapa funkcji jest dobrym wyborem w przypadku tablic obiektów. Chociaż opublikowano już wiele dobrych odpowiedzi, pomocne może być użycie mapy w połączeniu z filtrem.

Jeśli chcesz wykluczyć właściwości, których wartości są niezdefiniowane lub wykluczyć tylko określoną właściwość, możesz wykonać następujące czynności:

    var obj = {value1: "val1", value2: "val2", Ndb_No: "testing", myVal: undefined};
    var keysFiltered = Object.keys(obj).filter(function(item){return !(item == "Ndb_No" || obj[item] == undefined)});
    var valuesFiltered = keysFiltered.map(function(item) {return obj[item]});

https://jsfiddle.net/ohea7mgk/


0

Powyższa odpowiedź jest dobra do wyodrębnienia pojedynczej właściwości, co jeśli chcesz wyodrębnić więcej niż jedną właściwość z tablicy obiektów. Oto rozwiązanie !! W takim przypadku możemy po prostu użyć _.pick (obiekt, [ścieżki])

_.pick(object, [paths])

Załóżmy, że obiekt objArray ma obiekty o trzech właściwościach, takich jak poniżej

objArray = [ { foo: 1, bar: 2, car:10}, { foo: 3, bar: 4, car:10}, { foo: 5, bar: 6, car:10} ];

Teraz chcemy wyodrębnić właściwości foo i bar z każdego obiektu i przechowywać je w osobnej tablicy. Najpierw wykonamy iterację elementów tablicy za pomocą mapy, a następnie zastosujemy na niej metodę Lodash Library Standard _.pick ().

Teraz możemy wyodrębnić właściwość „foo” i „bar”.

var newArray = objArray.map((element)=>{ return _.pick(element, ['foo','bar'])}) console.log(newArray);

a wynikiem byłoby [{foo: 1, takt: 2}, {foo: 3, takt: 4}, {foo: 5, takt: 6}]

cieszyć się!!!


1
Skąd _.pickpochodzi? To nie jest standardowa funkcja.
neves

Właśnie zaktualizowałem odpowiedź, _.pick () jest standardową metodą biblioteki Lodash.
Akash Jain

0

Jeśli masz zagnieżdżone tablice, możesz sprawić, aby działało w następujący sposób:

const objArray = [ 
     { id: 1, items: { foo:4, bar: 2}},
     { id: 2, items: { foo:3, bar: 2}},
     { id: 3, items: { foo:1, bar: 2}} 
    ];

    let result = objArray.map(({id, items: {foo}}) => ({id, foo}))
    
    console.log(result)

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.