关于javascript:从对象数组中,将属性的值提取为数组

From an array of objects, extract value of a property as array

我有以下结构的javascript对象数组:

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

我想从每个对象中提取一个字段,并得到一个包含这些值的数组,例如,字段foo将给出数组[ 1, 3, 5 ]

我可以用这种简单的方法做到这一点:

1
2
3
4
5
6
7
8
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 ]

有没有更优雅或更惯用的方法来实现这一点,这样就不需要自定义实用程序功能了?

注意关于建议的复制,它涉及如何将单个对象转换为数组。


以下是实现这一目标的较短方法:

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

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

您还可以查看MDN上的Array.prototype.map()文档


是的,但它依赖于JavaScript的ES5特性。这意味着它在IE8或更高版本中不起作用。

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

在ES6兼容的JS解释器上,您可以使用箭头函数来简化:

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

文档


查看lodash的_.pluck()函数或下划线的_.pluck()函数。两者都可以在单个函数调用中执行您想要的操作!

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

更新:从Lodash v4.0.0开始,_.pluck()已被删除,有利于_.map()与NIET的答案类似。_.pluck()仍在下划线中可用。

更新2:正如Mark在评论中指出的,在Lodash v4和4.3之间的某个地方,添加了一个新的函数,它再次提供了这个功能。_.property()是一个速记函数,它返回一个函数,用于获取对象中属性的值。

此外,_.map()现在允许将字符串作为第二个参数传入,该参数将传入_.property()。因此,以下两行相当于上述来自于预罗达什4的代码样本。

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

_.property()_.map()也允许您提供一个点分隔的字符串或数组,以便访问子属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
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]

如果您对函数式编程有更多的了解,请看一下Ramda的R.pluck()函数,它看起来像这样:

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


说到纯JS的解决方案,我发现一个简单的索引for循环虽然不美观,但它比它的替代方案更具有性能。

https://jspef.com/extract-prop-from-object-array/

从100000元素数组中提取单个属性

传统for循环368次/秒

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

ES6用于303次循环的..操作/秒

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

array.prototype.map 19次/秒

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

编辑:Ops/s更新于2017年10月。tl;dr-.map()速度慢。但有时可读性比性能更重要。


使用Array.prototype.map

1
2
3
4
5
function getFields(input, field) {
    return input.map(function(o) {
        return o[field];
    });
}

有关pre-ES5浏览器的填充程序,请参见上面的链接。


最好使用像lodash或下划线这样的库来保证跨浏览器的安全。

在Lodash中,可以通过以下方法获取数组中属性的值

1
_.map(objArray,"foo")

在下划线中

1
_.pluck(objArray,"foo")

两者都将返回[1,3,5]


在ES6中,您可以执行以下操作:

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

虽然map是从对象列表中选择"列"的合适解决方案,但它有一个缺点。如果没有明确检查列是否存在,它将抛出一个错误并(最多)向您提供undefined。我会选择一个reduce解决方案,它可以简单地忽略属性,甚至为您设置一个默认值。

1
2
3
4
5
6
7
8
9
10
11
12
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;
    }, []);
}

JSB实例

即使所提供列表中的某个项不是对象或不包含字段,也可以执行此操作。

如果项目不是对象或不包含字段,则可以通过协商默认值使其更灵活。

1
2
3
4
5
6
7
8
9
10
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;
    }, []);
}

JSB实例

这与map相同,因为返回的数组的长度与提供的数组的长度相同。(在这种情况下,mapreduce便宜一点):

1
2
3
4
5
6
7
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;
    }, []);
}

JSB实例

还有一个最灵活的解决方案,它可以让你通过提供一个替代值在两种行为之间切换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
    }, []);
}

JSB实例

正如上面的例子(希望)揭示了这种工作方式,让我们通过使用Array.concat函数来稍微缩短函数。

1
2
3
4
5
6
7
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 : []));
    }, []);
}

JSB实例


这取决于你对"更好"的定义。

其他答案指出了地图的使用,这是自然的(特别是对于习惯了功能风格的人)和简洁的。我强烈建议你使用它(如果你不为几个IE8-IT的家伙费心的话)。所以,如果"更好"的意思是"更简洁"、"可维护"、"可理解",那么是的,这就更好了。

另一方面,这种美不可能没有额外的成本。我不喜欢微生物,但我在这里做了一个小测试。结果是可以预测的,旧的丑陋的方法似乎比映射函数更快。所以,如果"更好"的意思是"更快",那么就不,保持传统的时尚。

再说一遍,这只是一个微生物,决不是反对使用map,这只是我的两分钱:)。


一般来说,如果您想推断数组中的对象值(如问题中所述),那么可以使用reduce、map和array析构化。

ES6

1
2
3
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)

在循环中等效使用如下:

1
2
3
4
5
6
7
for (let i in a) {
  let temp = [];
  for (let j in a[i]) {
    temp.push(a[i][j]);
  }
  array.push(temp);
}

产生的输出:["word","again","some","1","2","3"]


如果还希望支持类似数组的对象,请使用array.from(es2015):

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

与array.prototype.map()方法相比,它的优势在于输入也可以是一个集合:

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


函数映射是处理对象数组时的一个好选择。尽管已经发布了许多好的答案,但是使用map和filter组合的示例可能会有所帮助。

如果要排除未定义值的属性或只排除特定属性,可以执行以下操作:

1
2
3
    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/ohea7mgkk/


如果您希望在ES6+中有多个值,则以下内容将有效

1
2
3
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(对象,[路径])

1
_.pick(object, [paths])

假设objarray具有以下三个属性的对象

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

现在我们要从每个对象中提取foo和bar属性,并将它们存储在单独的数组中。首先,我们将使用map迭代数组元素,然后对其应用lodash库标准的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]

享受!!!!