Ответ 1
Вот более короткий способ достижения этого:
let result = objArray.map(a => a.foo);
или же
let result = objArray.map(({ foo }) => foo)
Вы также можете проверить Array.prototype.map()
.
У меня есть массив объектов 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 ]
Есть ли более элегантный или идиоматический способ сделать это, чтобы пользовательская функция полезности была бы ненужной?
Обратите внимание на предложенный дубликат, в нем рассказывается, как преобразовать отдельный объект в массив.
Вот более короткий способ достижения этого:
let result = objArray.map(a => a.foo);
или же
let result = objArray.map(({ foo }) => foo)
Вы также можете проверить Array.prototype.map()
.
Да, но он основан на функции ES5 JavaScript. Это означает, что он не будет работать в IE8 или старше.
var result = objArray.map(function(a) {return a.foo;});
На ES6-совместимых интерпретаторах JS вы можете использовать функцию стрелки для краткости:
var result = objArray.map(a => a.foo);
Проверьте функцию Lodash _.pluck()
или функцию Underscore _.pluck()
. Оба делают именно то, что вы хотите в одном вызове функции!
var result = _.pluck(objArray, 'foo');
Обновление: _.pluck()
было удалено с Lodash v4.0.0 в пользу _.map()
в сочетании с чем-то похожим на ответ Niet. _.pluck()
все еще доступен в Underscore.
Обновление 2: Как отмечает Марк в комментариях, где-то между Lodash v4 и 4.3 добавлена новая функция, которая снова предоставляет эту функциональность. _.property()
- это сокращенная функция, которая возвращает функцию для получения значения свойства в объекте.
Кроме того, _.map()
теперь позволяет передать строку в качестве второго параметра, который передается в _.property()
. В результате следующие две строки эквивалентны приведенному выше образцу кода из pre-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]
.
Если вы немного больше в функциональном программировании, взгляните на функцию R.pluck()
, которая будет выглядеть примерно так:
var result = R.pluck('foo')(objArray); // or just R.pluck('foo', objArray)
Говоря о решениях только для JS, я обнаружил, что, как ни крути, простой индексированный цикл for
более производительный, чем его альтернативы.
Извлечение одного свойства из массива элементов 100000 (через jsPerf)
Традиционный для цикла 368 Ops/sec
var vals=[];
for(var i=0;i<testArray.length;i++){
vals.push(testArray[i].val);
}
ES6 для... 303 Ops/сек
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 не работает, удалена.
Использование Array.prototype.map
:
function getFields(input, field) {
return input.map(function(o) {
return o[field];
});
}
См. приведенную выше ссылку для прокладки для предварительных браузеров ES5.
Лучше использовать какие-то библиотеки, такие как lodash или underscore для межбраузерной гарантии.
В Lodash вы можете получить значения свойства в массиве следующим способом
_.map(objArray,"foo")
и в нижней части
_.pluck(objArray,"foo")
Оба вернутся
[1, 2, 3]
В ES6 вы можете:
const objArray = [{foo: 1, bar: 2}, {foo: 3, bar: 4}, {foo: 5, bar: 6}]
objArray.map(({ foo }) => foo)
Хотя 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;
}, []);
}
Это будет работать, даже если один из элементов в предоставленном списке не является объектом или не содержит поле.
Это может быть даже сделано более гибким путем согласования значения по умолчанию, если элемент не является объектом или не содержит поле.
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;
}, []);
}
Это будет то же самое с картой, так как длина возвращаемого массива будет такой же, как предоставленный массив. (В этом случае 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;
}, []);
}
Кроме того, существует наиболее гибкое решение, позволяющее переключаться между двумя вариантами поведения, просто предоставляя альтернативное значение.
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;
}, []);
}
Поскольку вышеприведенные примеры (надеюсь) проливают некоторый свет на то, как это работает, давайте немного Array.concat
функцию, используя функцию 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 : []));
}, []);
}
Это зависит от вашего определения "лучше" .
Другие ответы указывают на использование карты, которая является естественной (особенно для парней, привыкших к функциональному стилю) и кратким. Я настоятельно рекомендую использовать его (если вы не беспокоитесь о нескольких ребятах из IE8-IT). Поэтому, если "лучше" означает "более краткий", "поддерживаемый", "понятный", то да, это лучше.
С другой стороны, эта красота не приходит без дополнительных затрат. Я не большой поклонник microbench, но я выставил здесь небольшой тест. Результат предсказуем, старый уродливый способ кажется быстрее, чем функция карты. Поэтому, если "лучше" означает "быстрее", то нет, оставайтесь со старой школьной манерой.
Опять же, это просто микрофон и никоим образом не защищает от использования map
, это всего лишь мои два цента:).
Карта функций - хороший выбор при работе с объектными массивами. Хотя уже было опубликовано несколько хороших ответов, может оказаться полезным пример использования карты с комбинацией с фильтром.
Если вы хотите исключить свойства, значения которых undefined или исключить только определенное свойство, вы можете сделать следующее:
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]});
Если вы хотите также поддерживать подобные массиву объекты, используйте Array.from (ES2015):
Array.from(arrayLike, x => x.foo);
Преимущество, которое у него есть над методом Array.prototype.map(), - это вход, также может быть Set:
let arrayLike = new Set([{foo: 1}, {foo: 2}, {foo: 3}]);
В общем случае, если вы хотите экстраполировать значения объектов, которые находятся внутри массива (как описано в вопросе), вы можете использовать деструкцию сокращения, карты и массива.
ES6
let a = [{ z: 'word', c: 'again', d: 'some' }, { u: '1', r: '2', i: '3' }];
let b = a.reduce((acc, x) => [...acc, Object.values(x).map((y, i) => y)], []);
console.log(b)
Эквивалентное использование для цикла in будет следующим:
for (let i in a) {
let temp = [];
for (let j in a[i]) {
temp.push(a[i][j]);
}
array.push(temp);
}
Производительность: ["слово", "снова", "некоторые", "1", "2", "3"]
Если вы хотите несколько значений в 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.
Приведенный выше ответ хорош для извлечения одного свойства, что если вы хотите извлечь более одного свойства из массива объектов. Вот решение !! В этом случае мы можем просто использовать _.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 из каждого объекта и сохранить их в отдельном массиве. Сначала мы будем перебирать элементы массива с помощью map, а затем применяем к нему метод 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}]
наслаждаться!!!