如何在JavaScript中从一个对象数组中获取不同的值?

842
假设我有以下内容:
var array = 
    [
        {"name":"Joe", "age":17}, 
        {"name":"Bob", "age":17}, 
        {"name":"Carl", "age": 35}
    ]

什么是获取所有不同年龄的数组的最佳方法,以便得到以下结果数组的最佳方式:
[17, 35]

有没有其他方法可以更好地结构化数据,而不必迭代每个数组检查“年龄”的值,并检查其是否存在于另一个数组中,如果不存在则添加它?
如果有一种方法可以只提取不同的年龄而不用迭代...
我希望改进当前低效的方法... 如果这意味着“数组”不是对象数组,而是具有某些唯一键(即“1,2,3”)的对象“映射”,那也没关系。我只是寻找最高性能效率的方式。
以下是我目前的做法,但对我来说,迭代似乎效率很低,尽管它确实有效...
var distinct = []
for (var i = 0; i < array.length; i++)
   if (array[i].age not in distinct)
      distinct.push(array[i].age)

74
迭代不是“效率低下的”,你不能“不迭代”就对每个元素进行操作。虽然你可以使用各种看起来像函数式的方法,但最终某个层面上的东西仍需要遍历项。 - Eevee
//100% 运行代码 const listOfTags = [{ id: 1, label: "你好", color: "红色", sorting: 0 }, { id: 2, label: "世界", color: "绿色", sorting: 1 }, { id: 3, label: "你好", color: "蓝色", sorting: 4 }, { id: 4, label: "阳光", color: "黄色", sorting: 5 }, { id: 5, label: "你好", color: "红色", sorting: 6 }], keys = ['label', 'color'], filtered = listOfTags.filter( (s => o => (k => !s.has(k) && s.add(k)) (keys.map(k => o[k]).join('|')) ) (new Set) );console.log(filtered); - Sandeep Mishra
1
赏金很高,但是使用给定数据和答案的问题已经在这里回答了:https://dev59.com/TlQJ5IYBdhLWcg3wlm7B。那么赏金的目的是什么?我应该用两个或更多键来回答这个特定的问题吗? - Nina Scholz
1
Set 对象和 map 是浪费的。这个任务只需要一个简单的 .reduce() 阶段即可。 - Redu
请查看此示例,https://stackoverflow.com/a/58944998/13013258。 - Kezia Rose
/答案:/ var ListDistinct=[]; array.forEach((m)=>{ if(ListDistinct.indexOf(m.age)<0)ListDistinct.push(m.age); }); console.log(ListDistinct); - garish
65个回答

1278
如果您使用ES6/ES2015或更高版本,您可以这样做:

const data = [
  { group: 'A', name: 'SD' }, 
  { group: 'B', name: 'FI' }, 
  { group: 'A', name: 'MM' },
  { group: 'B', name: 'CO'}
];
const unique = [...new Set(data.map(item => item.group))]; // [ 'A', 'B']

这里 有一个关于如何做到的示例。


12
我遇到了一个错误:TypeError: (中间的值).slice 不是一个函数 - null canvas
4
谢谢,但是“...”是什么意思? - Thomas
11
@Thomas ... 意味着展开运算符。在这种情况下,它意味着创建新的集合并将其展开到列表中。您可以在此处了解更多信息:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator - Vlad Bezden
18
对于 TypeScript,您需要在 Array.from() 中使用新的 Set 包装...我会在下面提供这个答案。@AngJobs - Christian Matthew
48
这个解决方案几乎是逐字翻译自@Russell Vea 几个月之前的回答。你是否查找了已有的答案?但是,266个赞表明没有其他人进行过检查。 - Dan Dascalescu
显示剩余2条评论

529

想要按键返回具有唯一属性的对象的人,可以参考以下内容:

const array =
  [
    { "name": "Joe", "age": 17 },
    { "name": "Bob", "age": 17 },
    { "name": "Carl", "age": 35 }
  ]

const key = 'age';

const arrayUniqueByKey = [...new Map(array.map(item =>
  [item[key], item])).values()];

console.log(arrayUniqueByKey);

   /*OUTPUT
       [
        { "name": "Bob", "age": 17 },
        { "name": "Carl", "age": 35 }
       ]
   */

 // Note: this will pick the last duplicated item in the list.


5
一个改进建议——在 map 调用之前添加 .filter(Boolean)。如果数组中的任何对象为 null,那么这将抛出一个 Cannot read properties of null (reading 'id') 的错误。 - Paul G
2
这个回答确实教会了我很多东西。谢谢!@ArunSaini - Robin Nelson
我会期望相反的情况,保留第一个并移除其他的,因为它们是重复的。 - To Kra
@ToKra 你可以使用 "array.reverse()" 来反转输入的数组,然后再使用 "arrayUniqueByKey.reverse()" 来反转输出的数组。 - Arun Saini

324

使用ES6

let array = [
  { "name": "Joe", "age": 17 },
  { "name": "Bob", "age": 17 },
  { "name": "Carl", "age": 35 }
];
array.map(item => item.age)
  .filter((value, index, self) => self.indexOf(value) === index)

> [17, 35]

6
如果我想通过索引获取值,我该怎么做? 例如:我想获取 {"name": "Bob", "age":"17"},{"name": "Bob", "age":"17"} 的值。 - Abdullah Al Mamun
77
这就是为什么你必须继续往下滑。 - Alejandro Bastidas
21
你可以在.filter中使用map而不是先调用.map,像这样:array.filter((value, index, self) => self.map(x => x.age).indexOf(value.age) == index)。这将帮助你更有效地筛选数组,并确保每个对象的age属性只出现一次。 - Leo
4
请注意大数组中 .filter 内的 .map 可能会导致性能下降,因此请谨慎使用。 - Leo
5
@IvanNosov的代码片段非常好,不过加上几行解释它如何使用指针来映射和过滤文档,会让初学者更容易理解。 - azakgaim
显示剩余5条评论

184

使用ES6功能,您可以这样做:

const uniqueAges = [...new Set( array.map(obj => obj.age)) ];

14
这种方法只适用于原始类型(在这里年龄是一个数字数组),但对于对象则无法奏效。 - k0pernikus
有什么办法可以使其适用于具有两个键的对象? - cegprakash
17
类似于 const uniqueObjects = [ ...new Set( array.map( obj => obj.age) ) ].map( age=> { return array.find(obj => obj.age === age) } ) - Harley B

181

以下是如何使用ES6中的新Set解决此问题,截至2017年8月25日

TypeScript

 Array.from(new Set(yourArray.map((item: any) => item.id)))

JS

->

JavaScript

 Array.from(new Set(yourArray.map((item) => item.id)))

3
这很有帮助,之前出了些问题,谢谢。 “Set”类型不是数组类型。 - Rusty Rob
1
这是一个很棒的答案。直到我滚动到这个答案之前,我打算写一个答案,说: Object.keys(values.reduce<any>((x, y) => {x[y] = null; return x; }, {}));但是这个答案更简洁,并适用于所有数据类型,而不仅仅是字符串。 - jgosar
1
非 TypeScript 版本:Array.from(new Set(yourArray.map((item) => item.id))) - Doomd
1
应该删除 typescript 部分,因为它不是语言的一部分,而且也是一个(暂时的)小众。 - vsync
OP代码中没有id,而且这个例子似乎是从你特定的用例中复制粘贴过来的,应该进行调整以包括所有可能的数组项,例如基元对象等。 - vsync
嗨@vsync,随意编辑或将您的更改放在这里,我会进行编辑。 TypeScript 的确很不错。 - Christian Matthew

158
你可以采用类似于这样的字典方法。基本上,你将想要使其不同的值分配为“字典”中的键(在这里,我们使用数组作为对象以避免使用字典模式)。如果该键不存在,则将该值添加为不同的值。
以下是一个可行的演示:

var array = [{"name":"Joe", "age":17}, {"name":"Bob", "age":17}, {"name":"Carl", "age": 35}];
var unique = [];
var distinct = [];
for( let i = 0; i < array.length; i++ ){
  if( !unique[array[i].age]){
    distinct.push(array[i].age);
    unique[array[i].age] = 1;
  }
}
var d = document.getElementById("d");
d.innerHTML = "" + distinct;
<div id="d"></div>

这将是O(n),其中n是数组中对象的数量,m是唯一值的数量。没有比O(n)更快的方法,因为您必须至少检查每个值一次。
之前的版本使用了一个对象和for in。这些都是次要的,并且已经在上面进行了小幅更新。然而,在原始jsperf中看起来性能有所提高的原因是数据样本大小太小了。因此,之前版本的主要比较是查看内部映射和过滤器使用与字典模式查找之间的差异。
我已经按照上述说明更新了代码,但我还将jsperf更新为查看1000个对象而不是3个。 3个忽略了许多涉及的性能陷阱(过时的jsperf)。
性能 https://jsperf.com/filter-vs-dictionary-more-data 当我运行它时,字典比filter快96%。

filter vs dictionary


5
快速且无需任何额外插件。真的很酷。 - mihai
2
如果这样怎么样:if( typeof(unique[array[i].age]) == "undefined"){ distinct.push(array[i].age); unique[array[i].age] = 0; } - Timeless
7
哇,性能真的更加偏向于地图选项了。点击那个 jsperf 链接! - A T
2
@98percentmonkey - 这个对象被用来帮助"字典方法"。在对象内,每个被追踪的出现都被加入作为一个键(或一个箱)。这样,当我们遇到一个出现时,我们可以检查看看这个键(或箱)是否存在;如果它确实存在,我们就知道这个出现不是唯一的——如果它不存在,我们就知道这个出现是独特的并将其添加进去。 - Travis J
2
使用对象的原因是查找的时间复杂度为O(1),因为只需要检查一次属性名称,而数组的时间复杂度为O(n),因为它必须查看每个值以检查是否存在。在过程结束时,对象中的键集合(bins)表示唯一出现的集合。 - Travis J
显示剩余13条评论

158
如果这是PHP,我会建立一个带有键的数组,并在结尾处使用array_keys,但JS没有这样的奢侈。相反,试试这个:
var flags = [], output = [], l = array.length, i;
for( i=0; i<l; i++) {
    if( flags[array[i].age]) continue;
    flags[array[i].age] = true;
    output.push(array[i].age);
}

17
不行,因为array_unique会比较整个项目,而不仅仅是像这里所要求的年龄。 - Niet the Dark Absol
10
我认为flags = {}flags = []更好。 - zhuguowei
3
或许,尽管稀疏数组没有什么问题——我也假定“age”是一个相对较小的整数(肯定小于120)。 - Niet the Dark Absol
我们如何打印出具有相同年龄的人的总数? - user1788736
@NiettheDarkAbsol 你会如何处理这样的问题?https://jsfiddle.net/BeerusDev/asgq8n7e/28/ 我有一个包含对象的数组,每个键Days都有一个数组。 - BeerusDev

73

我只会映射并移除重复项:

var ages = array.map(function(obj) { return obj.age; });
ages = ages.filter(function(v,i) { return ages.indexOf(v) == i; });

console.log(ages); //=> [17, 35]

编辑:好吧!在性能方面不是最有效的方式,但我认为这是最简单易读的方式。如果你真的关心微观优化或者有大量的数据,那么使用普通的for循环会更加“高效”。


7
@elclanrs说的是“最高性能效率的方法”,但这种方法实际上很 - Travis J
2
@Eevee:我明白了。你回答中的下划线版本也不是很快,意思是,最终你选择更方便的方式,我怀疑1-30%的差异是否能在通用测试中体现出"巨大的改进",特别是在操作数达到数千时。 - elclanrs
4
好的,只有三个元素时,最快的方法是创建三个变量并使用一些 if 语句。但是当元素数量达到三百万时,你会得到非常不同的结果。 - Eevee
3
虽然不快,但在我的情况下已经足够了,因为我没有处理大型数据集,谢谢。 - Felipe Alarcon
这不是O(N^2)吗?不建议使用。你需要使用一些内部查找的常数时间,比如map。 - drone1

58
var unique = array
    .map(p => p.age)
    .filter((age, index, arr) => arr.indexOf(age) == index)
    .sort(); // sorting is optional

// or in ES6

var unique = [...new Set(array.map(p => p.age))];

// or with lodash

var unique = _.uniq(_.map(array, 'age'));

ES6例子

const data = [
  { name: "Joe", age: 17}, 
  { name: "Bob", age: 17}, 
  { name: "Carl", age: 35}
];

const arr = data.map(p => p.age); // [17, 17, 35]
const s = new Set(arr); // {17, 35} a set removes duplications, but it's still a set
const unique = [...s]; // [17, 35] Use the spread operator to transform a set into an Array
// or use Array.from to transform a set into an array
const unique2 = Array.from(s); // [17, 35]

6
虽然这段代码可能回答了问题,但是提供关于它如何解决问题以及为什么能够解决问题的额外背景信息,将会提高答案的长期价值。 - Alexander
第一个使用indexOf与index的方法真是太棒了。 - alas
请注意,这仅适用于原始值。如果您有一个日期数组,则需要一些更定制的方法。在此处阅读更多信息:https://codeburst.io/javascript-array-distinct-5edc93501dc4 - Molx

53

如果您需要整个对象,这是ES6版本的轻微变化:

let arr = [
    {"name":"Joe", "age":17}, 
    {"name":"Bob", "age":17}, 
    {"name":"Carl", "age": 35}
]
arr.filter((a, i) => arr.findIndex((s) => a.age === s.age) === i) // [{"name":"Joe", "age":17}, {"name":"Carl", "age": 35}]

我执行了这个程序,但它没有按照描述进行过滤,结果是// [{"name":"Joe", "age":17}]。 - G. I. Joe
已正确更新筛选参数为“年龄”。感谢您的发现。 - Ciprian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接