对象数组中的indexOf方法?

702

如何简单直接地找到满足某些条件的对象数组中的索引?

例如,给定以下输入:

var hello = { hello: 'world', foo: 'bar'};
var qaz = { hello: 'stevie', foo: 'baz'}

var myArray = [];
myArray.push(hello, qaz);

我该如何搜索myArray以找到其元素中hello属性等于'stevie'的索引(在这种情况下,结果应为1)?


1
你想要合并两个对象 helloqaz 吗? - Armin
不,我不想要。我想在一个数组中有一个对象列表。 - Antonio Laguna
好的!您想知道数组中具有已定义属性的整个对象的位置。 - Armin
13
我发现了一个非常简单的函数来解决这个问题,使用这个stackoverflow答案: var elementPos = array.map(function(x) {return x.id; }).indexOf(idYourAreLookingFor); var objectFound = array[elementPos]; - Rick
1
ES6的Array.indexOf比被广泛接受的答案更好(如果ES6适用于您)-请参见下面的完整示例。 - yar1
29个回答

1360

我认为您可以使用map函数在一行中解决它:

const pos = myArray.map(e => e.hello).indexOf('stevie');

65
这应该是被认可的答案。现在大多数浏览器都支持 Array.prototype.map() 方法。 - AlbertEngelB
11
如果不需要兼容IE8,那么这是最佳解决方案,但IE8不支持此方法。 - Antonio Laguna
84
嗯...值得注意的是,Array.prototype.map()方法会创建一个包含映射后元素的全新数组。因此,如果你有一个包含1000个元素的数组,首先会创建另一个有1000个元素的数组,然后再搜索它?我认为比较一下这种方法和简单的for循环的性能是值得的,特别是当你在资源有限的移动平台上运行时。 - Doug
8
尽管你提到的性能问题确实正确,但是谁会在没有找到瓶颈之前,为一个几乎完全受IO/网络约束的应用程序用七行代码替换一行呢? - Jared Smith
19
技术上来说,一个压缩过的JS文件也可以是一行代码;D - greaterKing
显示剩余15条评论

525

Array.prototype.findIndex 在除了 IE(非 Edge 版本)之外的所有浏览器中都支持。但是提供的polyfill很好用。

var indexOfStevie = myArray.findIndex(i => i.hello === "stevie");

使用map的解决方案是可以的。但是你每次搜索时都要遍历整个数组。这对于findIndex来说只有在找到匹配项后才停止迭代,所以这是最坏情况。


其实没有一个简洁的方式(当开发人员需要考虑IE8时),但这是一个常见的解决方案:

var searchTerm = "stevie",
    index = -1;
for(var i = 0, len = myArray.length; i < len; i++) {
    if (myArray[i].hello === searchTerm) {
        index = i;
        break;
    }
}

或者作为一个函数:

function arrayObjectIndexOf(myArray, searchTerm, property) {
    for(var i = 0, len = myArray.length; i < len; i++) {
        if (myArray[i][property] === searchTerm) return i;
    }
    return -1;
}
arrayObjectIndexOf(arr, "stevie", "hello"); // 1

一些注意事项:

  1. 不要在数组上使用for...in循环
  2. 确保在找到“needle”后跳出循环或从函数中返回
  3. 小心处理对象的相等性

例如,

var a = {obj: 0};
var b = [a];
b.indexOf({obj: 0}); // -1 not found

该函数的搜索项比较错误,应该是searchTerm :) - Antonio Laguna
有多个发生的情况。 - Joe
3
@SteveBennett 这是一个性能优化版本;数组的长度只需要在初始化for循环变量时确定一次。在你的情况下,每次迭代都会重新检查数组长度。 另请参阅https://dev59.com/mG435IYBdhLWcg3wjwzS以及https://dev59.com/F2oy5IYBdhLWcg3wlvGX但是,如果性能不是高优先级,那么这并不重要。 - loother
1
回答不错,但我进行了一些性能基准测试(请参见 https://jsperf.com/find-index-of-object-in-array-by-contents),发现这里提到的基于函数的答案似乎是第二个最高效的答案。唯一更高效的方法是将其放入原型中,而不仅仅是一个函数,如[我的答案](https://dev59.com/UGoy5IYBdhLWcg3wQr5i#44790688)中所述。 - Uniphonic
Polyfill 链接已失效! - Mehdi Dehghani

152

在ES2015中,这很容易实现:

myArray.map(x => x.hello).indexOf('stevie')

或者,对于更大的数组可能会有更好的性能:

myArray.findIndex(x => x.hello === 'stevie')

2
使用ES6的好方法。 - kag
我很惊讶,这两种方法都不如我的答案中提到的原型for循环高效。尽管findIndex方法在浏览器支持方面有些欠缺,但它似乎会做同样的事情,但最终效率却更低?请参见我的答案中的链接以获取基准测试结果。 - Uniphonic
如果任务的性能很重要,这很值得知道。根据我的经验,这很少发生,但效果因人而异。 - Steve Bennett
@Uniphonic - 2021年findIndex方法已被所有浏览器支持,除了OperaMini和IE - https://caniuse.com/array-find-index - Mauricio Gracia Gutierrez

25
var idx = myArray.reduce( function( cur, val, index ){

    if( val.hello === "stevie" && cur === -1 ) {
        return index;
    }
    return cur;

}, -1 );

17

我喜欢Pablo的答案,但是Array#indexOf和Array#map在所有浏览器上都不起作用。如果原生代码可用,Underscore将使用它,但也有备选方案。此外,它还具有pluck方法,可以完全执行Pablo的匿名映射方法。

var idx = _.chain(myArray).pluck("hello").indexOf("Stevie").value();

1
Array.prototype.map() 支持 IE9+,您可以使用 Polyfill 来支持 IE8、7、6: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/map#Polyfill - Johann Echavarria
1
你可以使用一个 polyfill……或者你可以直接使用 underscore 或 lodash,它们基本上是带有许多其他好处的 polyfill。那么对于 underscore 的反对意见是什么?大小吗? - tandrewnichols
我非常喜欢Underscore,你的回答也很聪明,但在我看来,Pablo的回答最干净。 - Johann Echavarria
哇,我从未想过可以这样使用链式编程。我真的喜欢它使搜索变得流畅的方式。 - Dylan Pierce
chain is superfluous here. _.pluck(myArray, 'hello').indexOf('stevie') - Steve Bennett

15

或者原型化它:

Array.prototype.indexOfObject = function arrayObjectIndexOf(property, value) {
    for (var i = 0, len = this.length; i < len; i++) {
        if (this[i][property] === value) return i;
    }
    return -1;
}

myArr.indexOfObject("name", "stevie");

9
非常方便!虽然我会选择使用 prototype.indexOfObject 方法,以避免干扰现有的 Array.indexOf 方法。Array.prototype.indexOfObject = function(property, value) { for (var i = 0, len = this.length; i < len; i++) { if (this[i][property] === value) return i; } return -1; }; - Adam
1
我会将其包装在自执行闭包中,并先存储旧的内容,替换函数的第一行应该是类似于 if (typeof property === 'string' || typeof property === 'number' || typeof property === 'boolean') return oldIndexOf(property, value); 的东西。这是因为这些类型是不可变的。我还会添加第三个参数以启用必要时回退到本机方法的功能。 - Claudia

10

虽然其他答案大多数都是正确的,但有时候,在你将要使用它的地方编写一个简短而简单的函数,可能是最好的选择。

// indexOf wrapper for the list of objects
function indexOfbyKey(obj_list, key, value) {
    for (index in obj_list) {
        if (obj_list[index][key] === value) return index;
    }
    return -1;
}
// Find the string in the list (default -1)
var test1 = indexOfbyKey(object_list, 'name', 'Stevie');
var test2 = indexOfbyKey(object_list, 'last_name', 'some other name');

这取决于你认为重要的是什么。使用一行代码或在某个地方放置通用解决方案可能会节省代码行数并非常巧妙,但有时候最好只是说:“我是这样做的”,而不是让未来的开发人员在反向工程上多花时间。特别是当你像你提问时自认为是“新手”的时候。


9
我比较了几种方法,并得出了解决这个问题最快的方法。它是一个“for”循环。它比任何其他方法都快5倍以上。
以下是测试页面:https://jsbench.me/9hjewv6a98

缺少 for-of 循环吗? - Douglas Gaskell
很遗憾,人们不会向下滚动两个答案。这是唯一好的答案。谢谢。 - HenrijsS

9
如果你的对象是在数组中使用的同一个对象,那么你应该能够以与字符串相同的方式获取该对象的索引。
var hello = {
    hello: 'world',
    foo: 'bar'
};
var qaz = {
    hello: 'stevie',
    foo: 'baz'
}

var qazCLONE = { // new object instance and same structure
    hello: 'stevie',
    foo: 'baz'
}

var myArray = [hello,qaz];

myArray.indexOf(qaz) // should return 1
myArray.indexOf(qazCLONE) // should return -1

1
这就是我一直在寻找的答案,因为我不确定IndexOf是按值匹配还是什么。现在我知道我可以使用IndexOf来查找我的对象,而不必担心是否有其他具有相同属性的对象。 - MDave

9

简介

myArray.indexOf('stevie','hello')

应用场景:

  /*****NORMAL****/  
[2,4,5].indexOf(4) ;//OUTPUT 1
 /****COMPLEX*****/
 [{slm:2},{slm:4},{slm:5}].indexOf(4,'slm');//OUTPUT 1
 //OR
 [{slm:2},{slm:4},{slm:5}].indexOf(4,function(e,i){
   return e.slm;
});//OUTPUT 1
/***MORE Complex**/
[{slm:{salat:2}},{slm:{salat:4}},{slm:{salat:5}}].indexOf(4,function(e,i){
   return e.slm.salat;
});//OUTPUT 1

API :

    Array.prototype.indexOfOld=Array.prototype.indexOf

    Array.prototype.indexOf=function(e,fn){
      if(!fn){return this.indexOfOld(e)}
      else{ 
       if(typeof fn ==='string'){var att=fn;fn=function(e){return e[att];}}
        return this.map(fn).indexOfOld(e);
      }
    };

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