método indexOf em uma matriz de objetos?

514

Qual é o melhor método para obter o índice de uma matriz que contém objetos?

Imagine este cenário:

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

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

Agora eu gostaria de ter o indexOfobjeto qual hellopropriedade é 'stevie'qual, neste exemplo, seria 1.

Sou bastante iniciante em JavaScript e não sei se existe um método simples ou se devo criar minha própria função para fazer isso.

Antonio Laguna
fonte
1
Deseja mesclar os dois objetos helloe qaz?
Armin
Não, eu não. Eu quero ter uma lista de objetos em uma matriz.
Antonio Laguna
Ah ok! Você deseja saber a posição de todo o objeto na matriz, que possui uma propriedade definida.
Armin
10
Eu encontrei uma função muito simples de resolver este problema exato com esta resposta SO: var elementPos = array.map(function(x) {return x.id; }).indexOf(idYourAreLookingFor); var objectFound = array[elementPos]; [link] ( stackoverflow.com/a/16100446/1937255 )
Rick
ES6 Array.indexOf é a resposta melhor que a aceita (se o ES6 funcionar para você) - veja o exemplo completo abaixo
yar1

Respostas:

1045

Eu acho que você pode resolvê-lo em uma linha usando a função de mapa :

pos = myArray.map(function(e) { return e.hello; }).indexOf('stevie');
Pablo Francisco Pérez Hidalgo
fonte
58
Honestamente, essa deve ser a resposta aceita. Atualmente, a maioria dos navegadores suportaArray.prototype.map()
AlbertEngelB
10
Não é suportado pelo IE8, mas, se isso não for um problema, esta é a melhor solução.
Antonio Laguna
57
Hum ... não vale a pena notar que Array.prototype.map () cria uma nova matriz contendo os itens mapeados? Então, se você tem uma matriz com 1000 elementos, criou outra matriz com 1000 elementos primeiro e depois a pesquisa? Acho que vale a pena ver o desempenho desse método versus um loop for simples. Especialmente quando você está executando em uma plataforma móvel com recursos limitados.
Doug
7
@ Doug Embora seu argumento sobre desempenho seja realmente correto, quem em sã consciência substituiria uma linha de código por sete por um aplicativo que é quase por definição IO / Network vinculado até que eles apresentassem gargalos?
Jared Smith
6
Tecnicamente um arquivo JS minified também é uma linha; D
greaterKing
371

Array.prototype.findIndex é suportado em todos os navegadores que não sejam o IE (sem borda). Mas o polyfill fornecido é bom.

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

A solução com o mapa está bem. Mas você está repetindo toda a matriz a cada pesquisa. Esse é apenas o pior caso para findIndex, que para de iterar quando uma correspondência é encontrada.


Não existe realmente uma maneira concisa (quando os desenvolvedores precisavam se preocupar com o IE8) , mas aqui está uma solução comum:

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

ou como uma função:

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

Apenas algumas notas:

  1. Não use para ... em loops em matrizes
  2. Certifique-se de sair do circuito ou retornar à função depois de encontrar sua "agulha"
  3. Cuidado com a igualdade de objetos

Por exemplo,

var a = {obj: 0};
var b = [a];
b.indexOf({obj: 0}); // -1 not found
Joe
fonte
a função tem o errado searchterm comparation como deveria ser searchTerm :)
Antonio Laguna
ocorreram várias ocorrências #
Joe Joe
3
@SteveBennett, é uma versão com desempenho otimizado; o comprimento da matriz deve ser determinado apenas uma vez (quando as variáveis ​​do loop for são inicializadas). No seu caso, o comprimento é verificado a cada iteração novamente. Consulte também stackoverflow.com/questions/5349425/… e stackoverflow.com/questions/8452317/… No entanto, se o desempenho não for alto, isso realmente não importa.
Loot:
1
Boa resposta, mas fiz alguns testes de desempenho (consulte jsperf.com/find-index-of-object-in-array-by-contents ) e constatei que a resposta baseada em função mencionada aqui parece ser a segunda resposta com melhor desempenho. A única coisa que tem mais desempenho acaba sendo colocá-lo em um protótipo, em vez de apenas em uma função, como mencionado na minha resposta .
Uniphonic
123

No ES2015, isso é bem fácil:

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

ou, provavelmente com melhor desempenho para matrizes maiores:

myArray.findIndex(x => x.hello === 'stevie')
Steve Bennett
fonte
2
Boa abordagem para usar ES6
kag
Fiquei surpreso que nenhum desses métodos seja tão eficiente quanto o loop for prototipado, como mencionado na minha resposta. Mesmo que o suporte ao navegador do método findIndex seja um pouco ruim, parece que ele faria a mesma coisa, mas ainda terá menos desempenho? Veja o link na minha resposta para referências.
Uniphonic
É bom saber se o desempenho é importante para a tarefa em questão. Na minha experiência, é muito raramente, mas sim.
Steve Bennett
24
var idx = myArray.reduce( function( cur, val, index ){

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

}, -1 );
Esailija
fonte
17

Eu gosto da resposta de Pablo, mas as matrizes # indexOf e Array # map não funcionam em todos os navegadores. O sublinhado usará o código nativo se estiver disponível, mas também possui fallbacks. Além disso, ele tem o método de arrancar para fazer exatamente o que o método de mapa anônimo de Pablo faz.

var idx = _.chain(myArray).pluck("hello").indexOf("Stevie").value();
tandrewnichols
fonte
1
Array.prototype.map () é compatível com o IE9 + e você pode usar um Polyfill para IE8, 7, 6: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Johann Echavarria
1
Você pode usar um polyfill. . . ou você pode usar sublinhado ou lodash, que são basicamente polyfills que têm vários outros itens em anexo . Qual é a objeção com o sublinhado? Tamanho?
Tandrewnichols
Eu realmente gosto de sublinhado, sua resposta também é inteligente, mas a resposta do IMHO Pablo é a mais limpa.
Johann Echavarria
Uau, eu nunca pensei em usar encadeamento assim. Eu realmente gosto de como torna a pesquisa fluente.
Dylan Pierce
chainé supérfluo aqui. _.pluck(myArray, 'hello').indexOf('stevie')
21818 Steve Bennett
14

Ou protótipo:

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");
Nathan Zaetta
fonte
9
Muito conveniente! Embora eu escolha prototype.indexOfObject para não interferir com o método Array.indexOf existente. 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
Eu o envolvia em um fechamento auto-executável, com o antigo sendo armazenado de antemão, com a primeira linha da função de substituição sendo algo parecido com if (typeof property === 'string' || typeof property === 'number' || typeof property === 'boolean') return oldIndexOf(property, value);. Isso ocorre porque esses são os poucos tipos que são imutáveis. Eu também destacaria um terceiro argumento para ativar o fallback do método nativo, se necessário.
Isiah Meadows
8

Breve

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

Casos de uso:

  /*****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);
      }
    };
Abdennour TOUMI
fonte
6

Fiz alguns testes de desempenho de várias respostas aqui, que qualquer pessoa pode executá-las:

https://jsperf.com/find-index-of-object-in-array-by-contents

Com base nos meus testes iniciais no Chrome, o método a seguir (usando um loop for configurado dentro de um protótipo) é o mais rápido:

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;
}

myArray.indexOfObject("hello", "stevie");

Este código é uma versão ligeiramente modificada da resposta de Nathan Zaetta.

Nos benchmarks de desempenho, tentei fazer isso com o destino no meio (índice 500) e no final (índice 999) de uma matriz de 1000 objetos, e mesmo se eu colocasse a meta como o último item da matriz (significando que ele precisa percorrer todos os itens da matriz antes de ser encontrado) ainda acaba sendo o mais rápido.

Essa solução também tem o benefício de ser uma das mais concisas para execução repetida, já que somente a última linha precisa ser repetida:

myArray.indexOfObject("hello", "stevie");
Uniphonic
fonte
2
Eu estava prestes a postar uma resposta a esta pergunta usando um violino com meus próprios testes, mas graças à sua resposta, não preciso mais. Eu só quero confirmar seus testes - cheguei ao mesmo resultado, mas usando um whileloop em vez de um fore performance.now(). Eu desejo que esta resposta foi upvoted mais e que eu vi isso antes, teria me salvou algum tempo ...
Yin Cognyto
5

Comparei vários métodos e recebi um resultado com a maneira mais rápida de resolver esse problema. É um forlaço. É mais de 5 vezes mais rápido que qualquer outro método.

Aqui está a página do teste: https://jsbench.me/9hjewv6a98

John Klimov
fonte
Faltando um for-ofloop não?
Douglas Gaskell
5

Embora, a maioria das outras respostas aqui sejam válidas. Às vezes, é melhor criar uma função simples e curta perto de onde você a usará.

// 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');

Depende do que é importante para você. Pode salvar linhas de código e ser muito inteligente usar uma linha, ou colocar uma solução genérica em algum lugar que cubra vários casos extremos. Mas às vezes é melhor apenas dizer: "aqui eu fiz assim" em vez de deixar que desenvolvedores futuros tenham um trabalho extra de engenharia reversa. Especialmente se você se considera "um novato" como em sua pergunta.

SpiRail
fonte
4
array.filter(function(item, indx, arr){ return(item.hello === 'stevie'); })[0];

Cuidado com o [0].

É apropriado usar reducecomo na Antonio Lagunaresposta.

Desculpas pela brevidade ...

Cody
fonte
4

Se o seu objeto for o mesmo que você está usando na matriz, você poderá obter o índice do objeto da mesma maneira que se fosse uma string.

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
Caio Koiti
fonte
Esta é a resposta que eu estava procurando, pois não estava claro para mim se IndexOf correspondia por valor ou o quê. Agora eu sei que posso usar IndexOf para encontrar meu objeto e não me preocupo se houver outros objetos com as mesmas propriedades.
MDave
3

simples:

myArray.indexOf(myArray.filter(function(item) {
    return item.hello == "stevie"
})[0])
abutre-grifo
fonte
3

Se você está interessado apenas em encontrar a posição, consulte a resposta de @ Pablo .

pos = myArray.map(function(e) { return e.hello; }).indexOf('stevie');

No entanto, se você está ansioso para encontrar o elemento (ou seja, se você estava pensando em fazer algo assim myArray[pos]), existe uma maneira de uma linha mais eficiente de fazê-lo, usando filter.

element = myArray.filter((e) => e.hello === 'stevie')[0];

Veja resultados de desempenho (~ + 42% ops / s): http://jsbench.github.io/#7fa01f89a5dc5cc3bee79abfde80cdb3

zurfyx
fonte
2

Veja este exemplo: http://jsfiddle.net/89C54/

for (i = 0; i < myArray.length; i++) {
    if (myArray[i].hello === 'stevie') {
        alert('position: ' + i);
        return;
    }
}

Começa a contar com zero.

Armin
fonte
2

Eu fiz uma função genérica para verificar o código abaixo e funciona para qualquer objeto

function indexOfExt(list, item) {
    var len = list.length;

    for (var i = 0; i < len; i++) {
        var keys = Object.keys(list[i]);
        var flg = true;
        for (var j = 0; j < keys.length; j++) {
            var value = list[i][keys[j]];
            if (item[keys[j]] !== value) {
                flg = false;
            }
        }
        if (flg == true) {
            return i;
        }
    }
    return -1;
}

var items = [{ "hello": 'world', "foo": 'bar' }];
var selectedItem = { "hello": 'world', "foo": 'bar' };
alert(items.indexOf(selectedItem));
alert(indexOfExt(items, selectedItem));

O primeiro alerta retornará -1 (significa que a correspondência não foi encontrada) e o segundo alerta retornará 0 (significa que a correspondência foi encontrada).

Shiljo Paulson
fonte
2

Use _.findIndexda biblioteca underscore.js

Aqui está o exemplo _.findIndex([{a:1},{a: 2,c:10},{a: 3}], {a:2,c:10}) //1

niren
fonte
Ao sugerir métodos de bibliotecas adicionais, você deve mencionar de onde eles vêm.
Craicerjack
@ Steve Bennett agradável use.of a biblioteca agora o seu lodash em
zabusa
2

Usando o findIndexmétodo ES6 , sem lodash ou qualquer outra biblioteca, você pode escrever:

function deepIndexOf(arr, obj) {
  return arr.findIndex(function (cur) {
    return Object.keys(obj).every(function (key) {
      return obj[key] === cur[key];
    });
  });
}

Isso irá comparar as propriedades imediatas do objeto, mas não se repetirá nas propriedades.

Se sua implementação ainda não fornecer findIndex(a maioria não fornece ), você poderá adicionar um polyfill leve que ofereça suporte a esta pesquisa:

function deepIndexOf(arr, obj) {
  function findIndex = Array.prototype.findIndex || function (pred) {
    for (let i = 0; i < this.length; ++i) {
      if (pred.call(this, this[i], i)) {
        return i;
      }
    }

    return -1;
  }

  return findIndex.call(arr, function (cur) {
    return Object.keys(obj).every(function (key) {
      return obj[key] === cur[key];
    });
  });
}

(da minha resposta sobre esse idiota )

ssube
fonte
2

Você pode usar uma função nativa e conveniente Array.prototype.findIndex()basicamente:

O método findIndex () retorna um índice na matriz, se um elemento na matriz atender à função de teste fornecida. Caso contrário, -1 é retornado.

Apenas uma observação: ele não é suportado no Internet Explorer, Opera e Safari, mas você pode usar um Polyfill fornecido no link abaixo.

Mais Informações:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex

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

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

var index = myArray.findIndex(function(element, index, array) {
  if (element.hello === 'stevie') {
    return true;
  }
});
alert('stevie is at index: ' + index);

GibboK
fonte
2

Além da resposta do @Monika Garg , você pode usar findIndex()(existe um polyfill para navegadores não suportados).

Vi que as pessoas votaram negativamente nesta resposta e espero que tenham feito isso devido à sintaxe errada, porque, na minha opinião, essa é a maneira mais elegante.

O método findIndex () retorna um índice na matriz, se um elemento na matriz atender à função de teste fornecida. Caso contrário, -1 é retornado.

Por exemplo:

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

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

var index = myArray.findIndex(function(element) {
  return element.hello == 'stevie';
});

alert(index);

Mosh Feu
fonte
O método parece elegante, mas não há suporte do IE de acordo com o MDN? developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
Jaakko Karhu
Tente usar o polyfill (link na resposta).
Mosh Feu
1

Esta é a maneira de encontrar o índice do objeto na matriz

    var myArray = [{  hello: 'world',
        foo: 'bar'
    },{
        hello: 'stevie',
        foo: 'baz'
    }];



    for (i = 0; i < myArray.length; i++) {
        if (myArray[i].hello === 'stevie') {
            alert('position: ' + i);
            return;
        }
    }
Asad Fida
fonte
0

Isso funciona sem código personalizado

var arr, a, found;
arr = [{x: 1, y: 2}];
a = {x: 1, y: 2};
found = JSON.stringify(arr).indexOf(JSON.stringify(a)) > - 1;
// found === true

Nota: isso não fornece o índice real, apenas informa se o seu objeto existe na estrutura de dados atual

Xeltor
fonte
1
Isso não é válido, pois não permite obter nenhum índice.
Antonio Laguna
0

Você pode simplesmente usar

const someId = 2;
const array = [{id:1}, {id:2}, {id:3}];
const index = array.reduce((i, item, index) => item.id === someId ? index : i, -1);
alert('someId ' + someId + ' is at index ' + index);

Sem sublinhado, não por, apenas uma redução.

7ynk3r
fonte
0
var hello = {hello: "world",  foo: "bar"};
var qaz = {hello: "stevie", foo: "baz"};
var myArray = [];
myArray.push(hello,qaz);

function indexOfObject( arr, key, value   ) {
    var j = -1;
    var result = arr.some(function(obj, i) { 
        j++;
        return obj[key] == value;
    })

    if (!result) {
        return -1;
    } else {
        return j;
    };
}

alert(indexOfObject(myArray,"hello","world"));
user3235365
fonte
Use Array algum método.
user3235365
-1

Você pode criar seu próprio protótipo para fazer isso:

algo como:

Array.prototype.indexOfObject = function (object) {
    for (var i = 0; i < this.length; i++) {
        if (JSON.stringify(this[i]) === JSON.stringify(object))
            return i;
    }
}
Janx da Venezuela
fonte
2
Má prática, quebra de encapsulamento: developer.mozilla.org/pt-BR/docs/Web/JavaScript/Guide/…
HMR
Isso também quebraria em objetos definidos recursivamente.
Joseph Coco
-2

Eu vou preferir usar o findIndex()método:

 var index = myArray.findIndex('hello','stevie');

index lhe dará o número do índice.

Monika Garg
fonte
1
Resposta, ortografia e recuo do código e :) estão errados?
Sachin Verma
1
O findIndex não está em nenhuma implementação padrão do javascript. Existe uma proposta futura (ecma 6) para esse método, mas sua assinatura não é assim. Por favor, esclareça o que você quer dizer (talvez o nome do método), forneça a declaração do método findIndex ou nomeie a biblioteca que você está usando.
Sebastien F.