从对象数组中提取属性值作为数组


1016

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

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

我想从每个对象中提取一个字段,并获取一个包含值的数组,例如field foo将给出array [ 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 ]

是否有更优雅或惯用的方式来执行此操作,从而不需要自定义实用程序功能?


关于建议的重复项的注释,它涵盖了如何将单个对象转换为数组。


3
Prototype库为Array原型添加了一个“ pluck”函数(我认为),因此您可以编写var foos = objArray.pluck("foo");
尖尖的

3
@hyde-jsperf.com/ map - vs- native - for- loop-请看一下,希望普通循环本身是一个好的解决方案
N20084753 2013年

1
@ N20084753为了公平测试,您还应该比较Array.prototype.map存在的本地函数
Alnitak

@Alnitak-是的,但是我不明白本机Array.prototype.map比本机for循环如何优化,我们需要遍历整个数组的任何方式。
N20084753 2013年

3
OP,相对于其他建议,我更喜欢您的方法。没错。

Answers:


1333

这是实现它的一种较短的方法:

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

要么

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

您也可以检查Array.prototype.map()


3
好吧,这与totymedli的另一个答案的评论相同,但是(尽管如此)(我认为)实际上比其他答案更好,所以...将其更改为可接受的答案。
海德


1
当然,这是允许的,但恕我直言,没有什么,使这个答案客观上更好,但它采用的是语法,不可用在你问的问题,而不是甚至在某些浏览器所支持的时间。我还要指出的是,此答​​案是在此答案发布将近一年之前对最初接受的答案进行评论的直接副本。
Alnitak

26
@Alnitak在我的观点使用较新的功能,客观的更好。这个摘要非常普遍,因此我不认为这是窃。将过时的答案固定在最前面并没有任何价值。
罗布

6
评论不是答案,imo如果有人发布评论而不是答案,那是他们自己的错,如果有人将其复制到答案中。
Aequitas '18年

618

是的,但是它依靠JavaScript的ES5功能。这意味着它将无法在IE8或更低版本中使用。

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

为了简便起见,在与ES6兼容的JS解释器上,可以使用箭头功能

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

Array.prototype.map文档


5
该解决方案似乎简洁明了,但比普通循环方法进行了优化。
N20084753 2013年

OP是否不想要一种方法来获取任何字段,而不仅仅是硬编码foo
Alnitak

2
在ES6中,您可以做到var result = objArray.map((a) => (a.foo));
黑色

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

4
@FaizKhan注意年份。该答案发布于10月25日... 2013。“接受的”答案发布于2017年10月11日。
Niet the Dark Absol's October

52

查看Lodash的_.pluck()功能或Underscore _.pluck()功能。两者都完全可以在单个函数调用中完成您想要的!

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

更新: _.pluck()从Lodash v4.0.0起已删除,支持_.map()与类似于Niet的answer的组合_.pluck()在Underscore中仍然可用

更新2:正如Mark 在注释中指出的那样,在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]

如果您更喜欢函数式编程,请看一下Ramda的 R.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');
Mark A. Fitzgerald

45

在谈到仅JS解决方案时,我发现,尽管可能不太优雅,但简单的索引for循环比其替代方案更具性能。

从100000元素数组中提取单个属性(通过jsPerf)

传统for循环 368 Ops / sec

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

ES6 for..of循环 303 Ops / sec

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

虽然了解基准测试结果很有用,但我认为此处不合适,因为它不能回答这个问题。要改善它,请添加真实答案,或者将其压缩为注释(如果可能)。
Ifedi Okonkwo

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相同,因为返回数组的长度与提供的数组相同。(在这种情况下,a map比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;
    }, []);
}

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

通常,如果要外推数组中的对象值(如问题中所述),则可以使用reduce,map和array解构。

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 in循环等效的是:

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

产生的输出: [“ word”,“再次”,“ some”,“ 1”,“ 2”,“ 3”]


3

这取决于您对“更好”的定义。

其他答案指出地图的使用是自然的(特别是对于习惯使用功能样式的人)并且简洁。我强烈建议使用它(如果您不打扰那些IE8-IT专家的话)。因此,如果“更好”的意思是“更简洁”,“可维护”,“可理解”,那么可以,那就更好了。

另一方面,这种美丽并非没有额外费用。我不是microbench的忠实拥护者,但是我在这里做了一个小测试。结果是可以预测的,旧的丑陋方法似乎比map函数要快。因此,如果“更好”的意思是“更快”,那么不,保留传统的流行。

同样,这只是一个微平台,绝不主张使用map,这只是我的两分钱:)。


好吧,这实际上是服务器端的,而不是浏览器中的,所以IE8无关紧要。map是的,这是最明显的方式,以某种方式我无法在google上找到它(我只是找到了jquery的地图,这并不相关)。
海德

3

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

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

Array.prototype.map()方法相比,它的优点是输入也可以是Set

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

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

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

    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属性,并将它们存储在单独的数组中。首先,我们将使用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}]

请享用!!!


1
哪里_.pick来的?这不是标准功能。
neves

我刚刚更新了答案,_.pick()是Lodash库的标准方法。
阿卡什·贾因

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)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.