З масиву об'єктів витягніть значення властивості як масив


1016

У мене є масив об’єктів JavaScript із такою структурою:

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

Я хочу витягнути поле з кожного об'єкта і отримати масив, що містить значення, наприклад поле fooдало б масив [ 1, 3, 5 ].

Я можу це зробити за допомогою цього тривіального підходу:

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 ]

Чи є більш елегантний чи ідіоматичний спосіб зробити це, щоб функція спеціальної утиліти була б непотрібною?


Зауважте про запропонований дублікат , він стосується способів перетворення одного об’єкта в масив.


4
Бібліотека Prototype додала функцію "pluck" до прототипу Array (я думаю), щоб ви могли писати var foos = objArray.pluck("foo");.
Pointy

3
@hyde - jsperf.com/map-vs-native-for-loop - будь ласка, подивіться на це, сподіваємось, звичайна петля хорошого рішення
N20084753

1
@ N20084753 для справедливого тестування слід також порівняти нативну Array.prototype.mapфункцію там, де вона існує
Alnitak

@Alnitak - так, ти маєш рацію, але я не можу зрозуміти, як нативна Array.prototype.map оптимізована, ніж нативна для циклу, будь-яким способом нам потрібно пройти повний масив.
N20084753

3
ОП, я віддаю перевагу вашому підходу до будь-яких інших, які були запропоновані. Нічого поганого в цьому немає.

Відповіді:


1334

Ось коротший спосіб її досягнення:

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

АБО

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

Ви також можете перевірити Array.prototype.map().


3
Ну, це те саме, що ще один коментар відповіді totymedli, але ні на що, це фактично краще (на мій погляд) спосіб, ніж в інших відповідях , так що ... Змінивши його на прийняту відповідь.
Гайд


1
звичайно, це дозволено, але в IMHO немає нічого, що робить цю відповідь об'єктивно кращою, за винятком того, що він використовує синтаксис, який був недоступний у той момент, коли ви задавали питання, і навіть не підтримується в деяких браузерах. Я також зазначу, що ця відповідь є прямою копією коментарів, які були зроблені за первісно прийнятою відповіддю майже за рік до публікації цієї відповіді.
Альнітак

26
@Alnitak Використовуючи нову функціональність, в моєї точки зору, це об'єктивно краще. Цей фрагмент надзвичайно поширений, тому я не переконаний, що це плагіат. Немає жодної цінності у збереженні застарілих відповідей, закріплених у верхній частині.
Роб

6
коментарі - це не відповіді, наприклад, якщо хтось розміщує коментар замість відповіді, це їхня вина, якщо хтось копіює його у відповідь.
Aequitas

618

Так, але він покладається на ES5-функцію JavaScript. Це означає, що він не працюватиме в IE8 або старіших версіях.

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

На перекладачах JS, сумісних з ES6, ви можете використовувати функцію стрілки для стислості:

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

Документація Array.prototype.map


5
Це рішення виглядає акуратним і простим, але воно оптимізовано, ніж метод простого циклу.
N20084753

не хоче ОП метод, щоб отримати будь-яке поле, а не просто жорстке кодування foo?
Альнітак

2
в ES6 можна зробитиvar result = objArray.map((a) => (a.foo));
Чорний

28
@Black Ще краще:var result = objArray.map(a => a.foo);
totymedli

4
@FaizKhan Зверніть увагу на рік. Ця відповідь була опублікована 25 жовтня 2013 року. Відповідь "прийнято" було розміщено 11 жовтня 2017 року. Якщо щось примітно, то галочка перейшла до іншої.
Niet the Dark Absol

52

Перевірте функцію Лодаша_.pluck() або функцію підкреслення_.pluck() . Обидва роблять саме те, що ви хочете в одному дзвінку функції!

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

Оновлення: _.pluck()було видалено з Lodash v4.0.0 , на користь _.map()у поєднанні з чимось подібним до відповіді Ніта . _.pluck()все ще доступний у підкресленні .

Оновлення 2: Як Марк зазначає у коментарях , десь між Lodash v4 та 4.3, була додана нова функція, яка знову забезпечує цю функціональність. _.property()- це скорочена функція, яка повертає функцію для отримання значення властивості в об'єкті.

Крім того, _.map()тепер дозволяє передавати рядок як другий параметр, який передається в _.property(). Як результат, наступні два рядки еквівалентні наведеному вище зразку коду з попереднього Lodash 4.

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

_.property(), а отже _.map(), також дозволяють надати рядок або масив, розділений крапкою, для доступу до суб властивостей:

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

Обидва _.map()дзвінки у наведеному вище прикладі повернуться [5, 2, 9].

Якщо ви трохи більше в курсі функціонального програмування, погляньте на функцію RamdaR.pluck() , яка виглядатиме приблизно так:

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

5
Хороша новина: десь між Lodash 4.0.0 та 4.3.0 _.property('foo')( lodash.com/docs#property ) додано як скорочення для function(o) { return o.foo; }. Це скорочує використання Lodash справа в var result = _.pluck(objArray, _.property('foo'));Для подальшої зручності Lodash 4.3.0 в _.map() метод також дозволяє стенографії , використовуючи _.property()під капотом, в результатіvar result = _.map(objArray, 'foo');
Марк А. Fitzgerald

45

Говорячи лише про рішення JS, я виявив, що, як би це не було елегантно, простий індексований forцикл є більш ефективним, ніж його альтернативи.

Вилучення однієї властивості з масиву 100000 елементів (через jsPerf)

Традиційний для циклу 368 Оп / сек

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

ES6 для ... циклу 303 Оп / сек

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

Array.prototype.map 19 Оп / сек

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

TL; DR - .map () повільний, але не соромтеся користуватися ним, якщо відчуваєте, що читабельність коштує більше, ніж продуктивність.

Редагувати №2: 6/2019 - посилання jsPerf розірвано, видалено.


1
Зателефонуйте мені, що вам подобається, але продуктивність налічується. Якщо ви можете зрозуміти карту, ви можете зрозуміти цикл for.
illcrx

Хоча корисно дізнатися про результати тестування, я вважаю, що тут це недоречно, оскільки це не дає відповіді на це питання. Щоб покращити його, або додайте реальну відповідь, або ж компресуйте її до коментаря (якщо можливо).
Іфдіді Оконкво

15

Використання Array.prototype.map:

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

Перегляньте вище посилання для перегляду веб-переглядачів до ES5.


15

Краще використовувати таку бібліотеку, як lodash або підкреслення, для впевненості у переглядачі.

У Lodash ви можете отримати значення властивості в масиві наступним методом

_.map(objArray,"foo")

і в підкресленні

_.pluck(objArray,"foo")

Обидва повернуться

[1, 2, 3]

10

У ES6 ви можете:

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

А як бути, якщо foo не визначено? Масив невизначених - це не добре.
godhar

6

Хоча mapце правильне рішення для вибору "стовпців" зі списку об'єктів, це має і зворотний бік. Якщо явно не перевірено, чи існують стовпці чи ні, це призведе до помилки та (у кращому випадку) надати вам undefined. Я б вибрав reduceрішення, яке може просто ігнорувати властивість або навіть встановити вам значення за замовчуванням.

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;
    }, []);
}

Приклад jsbin

Це буде працювати, навіть якщо один із елементів у поданому списку не є об’єктом або не містить поля.

Це може бути навіть більш гнучким шляхом узгодження значення за замовчуванням, якщо елемент не є об'єктом або не містить поля.

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;
    }, []);
}

Приклад jsbin

Це було б те саме, що і з картою, оскільки довжина повернутого масиву була б такою ж, як і наданий масив. (У такому випадку а mapтрохи дешевше, ніж а 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;
    }, []);
}

Приклад jsbin

І тоді є найбільш гнучке рішення, яке дозволяє перемикатися між обома способами поведінки, просто надаючи альтернативне значення.

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;
    }, []);
}

Приклад jsbin

Оскільки вищенаведені приклади (сподіваємось) проливають трохи світла на те, як це працює, дозволяє трохи скоротити функцію, використовуючи Array.concatфункцію.

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 : []));
    }, []);
}

Приклад jsbin


6

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

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)

Еквівалентом використання в циклі було б:

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

Отриманий результат: ["слово", "знову", "деякі", "1", "2", "3"]


3

Це залежить від вашого визначення поняття "краще".

Інші відповіді вказують на використання карти, яка є природною (особливо для хлопців, яка звикла до функціонального стилю) та стислою. Я настійно рекомендую використовувати його (якщо ви не турбуєтесь з кількома хлопцями IE8-IT). Тож якщо "краще" означає "більш лаконічний", "ремонтопридатний", "зрозумілий", то так, це краще.

З іншого боку, ця краса не виходить без додаткових витрат. Я не великий шанувальник мікробенка, але я поставив тут невеликий тест . Результат передбачуваний, старий потворний спосіб здається швидшим, ніж функція карти. Тож якщо "краще" означає "швидше", то ні, залишайтеся зі старої шкільної моди.

Знову ж таки, це просто мікробійка і жодним чином не виступає проти використання map, це лише два мої центи :).


Ну, це насправді сторона сервера, а не в браузері, тому IE8 не має значення. Так, mapце очевидний шлях, я чомусь просто не зміг його знайти в Google (я знайшов карту jquery, що не стосується).
Гайд

3

Якщо ви також хочете підтримувати масиви, схожі на об’єкти, використовуйте Array.from (ES2015):

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

Перевага, яку він має над методом Array.prototype.map () - вхід може також бути набором :

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

2

Якщо ви хочете кілька значень у ES6 +, наступне буде працювати

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

Це працює так, як {foo, baz}зліва використовується руйнування об'єктів, а праворуч стрілка еквівалентна {foo: foo, baz: baz}завдяки покращеним літературним об'єктам ES6 .


саме те, що мені було потрібно, як швидко / повільно, на ваш погляд, це рішення?
Рубен

1
@Ruben Справді сказати, я думаю, вам потрібно буде скористатися різними варіантами з цієї сторінки та поставити їх на тести, використовуючи щось на зразок jsperf.com
Chris Magnuson

1

Карта функцій - хороший вибір при роботі з об’єктними масивами. Хоча вже було розміщено ряд хороших відповідей, може бути корисним приклад використання карти з комбінацією з фільтром.

Якщо ви хочете виключити властивості, значення яких не визначені, або виключити лише певне властивість, ви можете зробити наступне:

    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

Наведена вище відповідь корисна для вилучення однієї властивості, що робити, якщо ви хочете витягти більше одного властивості з масиву об’єктів. Ось рішення !! У разі цього ми можемо просто використовувати _.pick (object, [paths])

_.pick(object, [paths])

Припустимо, у objArray є об'єкти з трьома властивостями, як показано нижче

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

Тепер ми хочемо витягти властивості foo та bar з кожного об'єкта і зберігати їх в окремому масиві. Спочатку ми повторимо елементи масиву за допомогою карти, а потім застосуємо до нього метод Lodash Library Standard _.pick ().

Тепер ми можемо витягти властивість 'foo' та 'bar'.

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

і результат був би [{foo: 1, bar: 2}, {foo: 3, bar: 4}, {foo: 5, bar: 6}]

насолоджуйся !!!


1
Звідки береться _.pick? Це не стандартна функція.
neves

Щойно я оновив відповідь, _.pick () - це стандартний метод бібліотеки Лодаша.
Акаш Джайн

0

Якщо ви вклали масиви, ви можете змусити його працювати так:

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)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.