méthode indexOf dans un tableau d'objets?

514

Quelle est la meilleure méthode pour obtenir l'index d'un tableau contenant des objets?

Imaginez ce scénario:

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

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

Maintenant, je voudrais avoir l' indexOfobjet dont la hellopropriété est 'stevie'qui, dans cet exemple, serait 1.

Je suis assez novice avec JavaScript et je ne sais pas s'il existe une méthode simple ou si je dois créer ma propre fonction pour le faire.

Antonio Laguna
la source
1
Voulez-vous fusionner les deux objets helloet qaz?
Armin
Non, je ne sais pas. Je veux avoir une liste d'objets dans un tableau.
Antonio Laguna
Ah ok! Vous voulez connaître la position de l'objet entier dans le tableau, qui a une propriété définie.
Armin
10
J'ai trouvé une fonction très simple pour résoudre ce problème exact avec cette réponse 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 est meilleure que la réponse acceptée (si ES6 fonctionne pour vous) - voir l'exemple complet ci
yar1

Réponses:

1045

Je pense que vous pouvez le résoudre en une seule ligne en utilisant la fonction de carte :

pos = myArray.map(function(e) { return e.hello; }).indexOf('stevie');
Pablo Francisco Pérez Hidalgo
la source
58
Cela devrait être honnêtement la réponse acceptée. La plupart des navigateurs supportent aujourd'huiArray.prototype.map()
AlbertEngelB
10
Il n'est pas pris en charge par IE8 mais, si ce n'est pas un problème, c'est la meilleure solution.
Antonio Laguna
57
Um ... ne vaut-il pas la peine de noter que Array.prototype.map () crée un tout nouveau tableau contenant les éléments mappés? Donc, si vous avez un tableau avec 1000 éléments, vous avez d'abord créé un autre tableau avec 1000 éléments, puis recherchez-le? Il vaudrait la peine, je pense, de voir les performances de cette méthode par rapport à une simple boucle for. Surtout lorsque vous utilisez une plate-forme mobile avec des ressources limitées.
Doug
7
@Doug Bien que votre point de vue sur les performances soit en effet correct, qui, dans son bon sens, remplacerait une ligne de code par sept pour une application qui est presque par définition liée aux E / S jusqu'à ce qu'ils aient profilé les goulots d'étranglement?
Jared Smith
6
Techniquement, un fichier js minifié est également une ligne; D
supérieurKing
371

Array.prototype.findIndex est pris en charge dans tous les navigateurs autres qu'IE (non-edge). Mais le polyfill fourni est agréable.

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

La solution avec la carte est correcte. Mais vous parcourez l'ensemble du tableau à chaque recherche. Ce n'est que le pire des cas pour findIndex qui cesse d'itérer une fois qu'une correspondance est trouvée.


Il n'y a pas vraiment de manière concise (lorsque les développeurs devaient se soucier d'IE8) , mais voici une solution courante:

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

ou en fonction:

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

Juste quelques notes:

  1. Ne pas utiliser pour ... dans les boucles sur les tableaux
  2. Assurez-vous de sortir de la boucle ou de revenir hors de la fonction une fois que vous avez trouvé votre "aiguille"
  3. Soyez prudent avec l'égalité des objets

Par exemple,

var a = {obj: 0};
var b = [a];
b.indexOf({obj: 0}); // -1 not found
Joe
la source
la fonction a la comparaison searchterm incorrecte comme elle devrait l'être searchTerm :)
Antonio Laguna
il y a eu plusieurs événements
Joe
3
@SteveBennett c'est une version optimisée pour les performances; la longueur du tableau ne doit être déterminée qu'une seule fois (lorsque les variables de la boucle for sont initialisées). Dans votre cas, la longueur est vérifiée à chaque itération. Voir aussi stackoverflow.com/questions/5349425/… et stackoverflow.com/questions/8452317/… Cependant, si les performances ne sont pas élevées, cela n'a pas vraiment d'importance.
loother
1
Bonne réponse, mais j'ai fait une analyse comparative des performances (voir jsperf.com/find-index-of-object-in-array-by-contents ), et j'ai constaté que la réponse basée sur les fonctions mentionnée ici semble être la deuxième réponse la plus performante. La seule chose plus performante finit par le mettre dans un prototype, au lieu d'une simple fonction, comme mentionné dans ma réponse .
Uniphonic
123

Dans ES2015, c'est assez simple:

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

ou, probablement avec de meilleures performances pour les baies plus grandes:

myArray.findIndex(x => x.hello === 'stevie')
Steve Bennett
la source
2
Bonne approche pour utiliser ES6
kag
J'ai été surpris qu'aucune de ces méthodes ne soit aussi performante que le prototype de boucle, comme mentionné dans ma réponse. Même si la prise en charge du navigateur de la méthode findIndex est un peu médiocre, il semble que ce serait la même chose, mais finit toujours moins performant? Voir le lien dans ma réponse pour les repères.
Uniphonic
Bon à savoir si les performances sont importantes pour la tâche à accomplir. C'est très rarement, d'après mon expérience, mais ymmv.
Steve Bennett
24
var idx = myArray.reduce( function( cur, val, index ){

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

}, -1 );
Esailija
la source
17

J'aime la réponse de Pablo, mais Array # indexOf et Array # map ne fonctionnent pas sur tous les navigateurs. Underscore utilisera du code natif s'il est disponible, mais a également des solutions de rechange. De plus, il a la méthode pluck pour faire exactement ce que fait la méthode de carte anonyme de Pablo.

var idx = _.chain(myArray).pluck("hello").indexOf("Stevie").value();
tandrewnichols
la source
1
Array.prototype.map () est compatible avec IE9 + et vous pouvez utiliser un Polyfill pour IE8, 7, 6: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Johann Echavarria
1
Vous pouvez utiliser un polyfill. . . ou vous pouvez simplement utiliser un trait de soulignement ou un lodash, qui sont essentiellement des polyfills qui ont tout un tas d'autres goodies attachés . Quelle est l'objection avec le soulignement? Taille?
tandrewnichols
J'aime vraiment Underscore, votre réponse est intelligente aussi, mais la réponse de IMHO Pablo est la plus propre.
Johann Echavarria
Wow, je n'ai jamais pensé à utiliser le chaînage comme ça. J'aime vraiment comment cela rend la recherche fluide.
Dylan Pierce
chainest superflu ici. _.pluck(myArray, 'hello').indexOf('stevie')
Steve Bennett
14

Ou prototype:

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
la source
9
Très pratique! Bien que je choisisse prototype.indexOfObject afin de ne pas interférer avec la méthode Array.indexOf existante. 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
Je l'envelopperais dans une fermeture auto-exécutable avec l'ancien étant stocké à l'avance, la première ligne de la fonction de remplacement étant quelque chose dans le sens de if (typeof property === 'string' || typeof property === 'number' || typeof property === 'boolean') return oldIndexOf(property, value);. C'est parce que ce sont les quelques types qui sont immuables. Je présenterais également un troisième argument pour permettre le retour à la méthode native si nécessaire.
Isiah Meadows
8

Bref

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

Cas d'utilisation:

  /*****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
la source
6

J'ai fait quelques tests de performances de différentes réponses ici, que tout le monde peut exécuter eux-mêmes:

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

D'après mes premiers tests dans Chrome, la méthode suivante (en utilisant une boucle for configurée dans un prototype) est la plus rapide:

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");

Ce code est une version légèrement modifiée de la réponse de Nathan Zaetta.

Dans les tests de performances, je l'ai essayé avec à la fois la cible au milieu (index 500) et à la fin (index 999) d'un tableau d'objets 1000, et même si je mets la cible en tant que tout dernier élément du tableau (ce qui signifie qu'il doit parcourir chaque élément du tableau avant qu'il ne soit trouvé), il finit toujours par être le plus rapide.

Cette solution a également l'avantage d'être l'une des plus laconiques pour une exécution répétée, car seule la dernière ligne doit être répétée:

myArray.indexOfObject("hello", "stevie");
Uniphonique
la source
2
J'étais sur le point de poster une réponse à cette question en utilisant un violon avec mes propres tests, mais grâce à votre réponse, je n'ai plus besoin de le faire. Je veux juste confirmer vos tests - je suis arrivé au même résultat, mais en utilisant une whileboucle au lieu d'une for, et performance.now(). J'aurais aimé que cette réponse soit plus votée et que je l'ai vue plus tôt, cela m'aurait fait gagner du temps ...
Yin Cognyto
5

J'ai comparé plusieurs méthodes et obtenu un résultat avec le moyen le plus rapide pour résoudre ce problème. C'est une forboucle. C'est 5 fois plus rapide que toute autre méthode.

Voici la page du test: https://jsbench.me/9hjewv6a98

John Klimov
la source
Vous manquez une for-ofboucle non?
Douglas Gaskell
5

Bien que la plupart des autres réponses ici soient valides. Parfois, il est préférable de créer une fonction simple et courte près de l'endroit où vous allez l'utiliser.

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

Cela dépend de ce qui est important pour vous. Cela pourrait économiser des lignes de code et être très intelligent pour utiliser une seule ligne, ou pour mettre une solution générique quelque part qui couvre divers cas de bord. Mais parfois, il vaut mieux simplement dire: «ici, je l'ai fait comme ça» plutôt que de laisser les futurs développeurs faire un travail d'ingénierie inverse supplémentaire. Surtout si vous vous considérez comme un "débutant" comme dans votre question.

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

Attention au [0].

Il convient d'utiliser reducecomme dans Antonio Lagunala réponse de.

Toutes mes excuses pour la brièveté ...

Cody
la source
4

Si votre objet est le même objet que ceux que vous utilisez dans le tableau, vous devriez pouvoir obtenir l'index de l'objet de la même manière que si c'était une chaîne.

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
la source
C'est la réponse que je cherchais car il n'était pas clair pour moi si IndexOf correspondait par valeur ou quoi. Maintenant, je sais que je peux utiliser IndexOf pour trouver mon objet, et ne vous inquiétez pas s'il y a d'autres objets avec les mêmes propriétés.
MDave
3

Facile:

myArray.indexOf(myArray.filter(function(item) {
    return item.hello == "stevie"
})[0])
vautour fauve
la source
3

Si vous souhaitez uniquement trouver le poste, consultez la réponse de @ Pablo .

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

Cependant, si vous avez hâte de trouver l'élément (c'est-à-dire si vous envisagez de faire quelque chose comme ça myArray[pos]), il existe un moyen plus efficace de le faire sur une seule ligne , en utilisant filter.

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

Voir les résultats de performance (~ + 42% ops / sec): http://jsbench.github.io/#7fa01f89a5dc5cc3bee79abfde80cdb3

zurfyx
la source
2

Voir cet exemple: http://jsfiddle.net/89C54/

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

Il commence à compter avec zéro.

Armin
la source
2

J'ai créé une fonction générique pour vérifier le code ci-dessous et fonctionne pour n'importe quel objet

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

La première alerte renverra -1 (signifie que la correspondance n'est pas trouvée) et la deuxième alerte renverra 0 (signifie que la correspondance a été trouvée).

Shiljo Paulson
la source
2

Utilisation à _.findIndexpartir de la bibliothèque underscore.js

Voici l'exemple _.findIndex([{a:1},{a: 2,c:10},{a: 3}], {a:2,c:10}) //1

niren
la source
Si vous suggérez des méthodes à partir de bibliothèques supplémentaires, vous devez mentionner d'où elles viennent.
Craicerjack
@Steve Bennett nice use.of the library now its its in lodash
zabusa
2

En utilisant la findIndexméthode ES6 , sans lodash ni aucune autre bibliothèque, vous pouvez écrire:

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

Cela comparera les propriétés immédiates de l'objet, mais ne récursif dans les propriétés.

Si votre implémentation ne fournit pas findIndexencore (la plupart ne le font pas), vous pouvez ajouter un polyfill léger qui prend en charge cette recherche:

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

(d'après ma réponse sur ce dupe )

ssube
la source
2

Vous pouvez utiliser une fonction native et pratique Array.prototype.findIndex()essentiellement:

La méthode findIndex () renvoie un index dans le tableau, si un élément du tableau satisfait la fonction de test fournie. Sinon, -1 est renvoyé.

Juste une note, il n'est pas pris en charge sur Internet Explorer, Opera et Safari, mais vous pouvez utiliser un Polyfill fourni dans le lien ci-dessous.

Plus d'information:

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
la source
2

En plus de la réponse de @Monika Garg , vous pouvez utiliser findIndex()(Il existe un polyfill pour les navigateurs non pris en charge).

J'ai vu que les gens ont rejeté cette réponse, et j'espère qu'ils l'ont fait à cause d'une mauvaise syntaxe, car à mon avis, c'est la manière la plus élégante.

La méthode findIndex () renvoie un index dans le tableau, si un élément du tableau satisfait la fonction de test fournie. Sinon, -1 est renvoyé.

Par exemple:

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
la source
La méthode semble élégante, mais pas de support IE selon MDN? developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
Jaakko Karhu
Essayez d'utiliser le polyfill (lien dans la réponse).
Mosh Feu
1

C'est la façon de trouver l'index de l'objet dans le tableau

    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
la source
0

Cela fonctionne sans code personnalisé

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

Remarque: cela ne donne pas l'index réel, il indique seulement si votre objet existe dans la structure de données actuelle

Xeltor
la source
1
Ceci n'est pas valide car cela ne permet pas d'obtenir d'index
Antonio Laguna
0

Vous pouvez simplement utiliser

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

Pas de soulignement, pas de, juste une réduction.

7ynk3r
la source
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
la source
Utilisez une méthode Array.
user3235365
-1

Vous pouvez créer votre propre prototype pour ce faire:

quelque chose comme:

Array.prototype.indexOfObject = function (object) {
    for (var i = 0; i < this.length; i++) {
        if (JSON.stringify(this[i]) === JSON.stringify(object))
            return i;
    }
}
Janx du Venezuela
la source
2
Mauvaise pratique, rupture de l'encapsulation: developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/…
HMR
Cela casserait également les objets définis de manière récursive.
Joseph Coco
-2

Je préférerai utiliser la findIndex()méthode:

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

index vous donnera le numéro d'index.

Monika Garg
la source
1
Réponse, orthographe et indentation de code et :) sont faux?
Sachin Verma
1
findIndex n'est dans aucune implémentation standard javascript. Il y a une proposition à venir (ecma 6) pour une telle méthode, mais sa signature n'est pas comme ça. Veuillez clarifier ce que vous voulez dire (peut-être le nom de la méthode), donner la déclaration de la méthode findIndex ou nommer la bibliothèque que vous utilisez.
Sebastien F.