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


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.