метод indexOf в масиві об'єктів?


512

Який найкращий спосіб отримати індекс масиву, який містить об’єкти?

Уявіть собі такий сценарій:

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

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

Тепер я хотів би мати indexOfоб'єкт, якою саме helloвластивістю буде 'stevie', у цьому прикладі 1.

Я досить новачок із JavaScript, і не знаю, чи існує простий метод, чи потрібно будувати власну функцію для цього.


1
Ви хочете об'єднати два об'єкти helloі qaz?
Армін

Ні, я цього не роблю. Я хочу мати список об’єктів у масиві.
Антоніо Лагуна

Ага гаразд! Ви хочете знати положення всього об’єкта в масиві, який має визначене властивість.
Армін

10
Я знайшов дуже просту функцію, щоб вирішити цю точну проблему за допомогою цієї відповіді ТА: var elementPos = array.map(function(x) {return x.id; }).indexOf(idYourAreLookingFor); var objectFound = array[elementPos]; [посилання] ( stackoverflow.com/a/16100446/1937255 )
Рік

ES6 Array.indexOf краще, ніж прийнята відповідь (якщо ES6 працює для вас) - див. Повний приклад нижче
yar1

Відповіді:


1042

Я думаю, ви можете вирішити це в один рядок за допомогою функції карти :

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

58
Це чесно має бути прийнятою відповіддю. Більшість браузерів сьогодні підтримуютьArray.prototype.map()
AlbertEngelB

10
Він не підтримується IE8, але, якщо це не проблема, це найкраще рішення.
Антоніо Лагуна

57
Гм ... чи не варто зазначати, що Array.prototype.map () створює цілком новий масив, що містить відображені елементи? Отже, якщо у вас є масив з 1000 елементами, ви спершу створили інший масив з 1000 елементами, а потім шукати його? Варто було б подумати, щоб побачити ефективність цього методу порівняно з простим для циклу. Особливо, коли ви працюєте на мобільній платформі з обмеженими ресурсами.
Даг

7
@Doug Хоча ваша думка щодо продуктивності дійсно правильна, хто з їхнього розуму замінить один рядок коду на сім для програми, яка майже за визначенням пов'язана IO / Network, поки вони не профілюються на вузьких місцях?
Джаред Сміт

6
Технічно мінімізований js-файл - це також один рядок; D
більшийКріч

371

Array.prototype.findIndex підтримується у всіх браузерах, окрім IE (не крайових). Але наданий поліфіл приємний.

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

Рішення з картою добре. Але ви повторюєте весь масив кожного пошуку. Це лише найгірший випадок для 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. Не використовуйте для ... у циклі на масивах
  2. Не забудьте вирватися з циклу або повернутися з функції, як тільки ви знайдете свою «голку»
  3. Будьте обережні при рівності об'єктів

Наприклад,

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

функція має порівняння searchterm неправильним, як це має бути searchTerm :)
Антоніо Лагуна

траплялися численні випадки
Джо

3
@SteveBennett - це версія, оптимізована за роботою; довжину масиву слід визначати лише один раз (коли ініціалізовані змінні для циклу for-циклу). У вашому випадку довжину перевіряють кожну ітерацію заново. Дивіться також stackoverflow.com/questions/5349425/… та stackoverflow.com/questions/8452317/… Однак, якщо продуктивність не висока, то це насправді не має значення.
мародерство

1
Хороша відповідь, але я зробив деякий показник ефективності (див. Jsperf.com/find-index-of-object-in-array-by-contents ), і виявив, що відповідь на основі функцій, згаданий тут, здається, другий найбільш ефективний відповідь. Єдине, що більш ефективне, в кінцевому підсумку - це введення його в прототип, а не просто функцію, про яку говорилося у моїй відповіді .
Уніфонічний

123

У ES2015 це досить просто:

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

або, можливо, з кращою продуктивністю для більших масивів:

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

2
Хороший підхід до використання ES6
KAG

Мене здивувало, що жоден із цих методів не є настільки ефективним, як прототип для циклу, як згадується у моїй відповіді. Навіть незважаючи на те, що підтримка браузера методу findIndex трохи погана, здається, що це робиться те саме, але все-таки стає менш ефективною? Дивіться посилання у моїй відповіді щодо орієнтирів.
Уніфонічний

Добре знати, чи важлива для виконання завдання важлива ефективність. На мій досвід, це дуже рідко, але ymmv.
Стів Беннетт

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

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

}, -1 );

17

Мені подобається відповідь Пабло, але Array # indexOf та Array # map працюють не у всіх браузерах. Підкреслення використовуватиме власний код, якщо він доступний, але також є резервні копії. Крім того, у нього є метод вибивання для того, щоб робити саме те, що робить метод анонімної карти Пабло.

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

1
Array.prototype.map () розміщується для IE9 +, і ви можете використовувати Polyfill для IE8, 7, 6: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Йоганн Ечаваррія

1
Можна використовувати поліфіл. . . або ви можете просто використати підкреслення або лодаш, які, в основному, є поліфілами, які мають цілу купу інших смаколиків . Що заперечує з підкресленням? Розмір?
tandrewnichols

Мені дуже подобається підкреслення, і ваша відповідь розумна, але відповідь ІМХО Пабло найясніша.
Йоганн Ечаваррія

Нічого собі, я ніколи не думав користуватися таким ланцюжком. Мені дуже подобається, як це робить пошук вільним.
Ділан Пірс

chainтут зайве. _.pluck(myArray, 'hello').indexOf('stevie')
Стів Беннетт

14

Або прототипуйте його:

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, щоб не заважати методу exisitng 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; };
Адам

1
Я обернув би це записом у самостійному виконанні закриття, коли попереднє зберігається заздалегідь, причому перший рядок функції заміни є чимось уздовж рядків if (typeof property === 'string' || typeof property === 'number' || typeof property === 'boolean') return oldIndexOf(property, value);. Це тому, що це кілька типів, які незмінні. Я б також запропонував третій аргумент, щоб увімкнути відпадання до рідного методу, якщо це потрібно.
Іся Медоуз

8

Короткий виклад

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

6

Тут я зробив кілька перевірок ефективності різних відповідей, які кожен може запустити самостійно:

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

На основі моїх початкових тестів у Chrome, найшвидший наступний метод (за допомогою циклу, встановленого всередині прототипу):

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

Цей код є дещо зміненою версією відповіді Натана Заетти.

У показниках ефективності я спробував це, причому ціль знаходиться в середині (індекс 500) і в самому кінці (індекс 999) масиву 1000 об'єктів, і навіть якщо я ставлю ціль як останній елемент у масиві (мається на увазі що він повинен проходити цикл через кожен окремий елемент у масиві, перш ніж його знайдуть) він все-таки закінчується найшвидше.

Це рішення також має перевагу як одне з найбільш стислих для багаторазового виконання, оскільки потрібно повторити лише останній рядок:

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

2
Я ось-ось збирався опублікувати відповідь на це питання, використовуючи загадку з власними тестами, але завдяки вашій відповіді мені більше не доведеться. Я просто хочу підтвердити ваші тести - я дійшов до того ж результату, але використовуючи whileцикл замість forодного, і performance.now(). Я б хотів, щоб ця відповідь була більш прихильною, і що я бачив її раніше, це врятувало б мені деякий час ...
Інь Когніто


5

Хоча більшість інших відповідей тут справедливі. Іноді, найкраще просто зробити коротку просту функцію біля місця, де ви її будете використовувати.

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

Це залежить від того, що для вас важливо. Це може зберегти рядки коду і бути дуже розумним, щоб використовувати однолінійку або розміщувати десь загальне рішення, яке охоплює різні кращі регістри. Але іноді краще просто сказати: "ось я це зробив так", а не залишати майбутнім розробникам зайві зворотні інженерні роботи. Особливо, якщо ви вважаєте себе "новачком", як у вашому питанні.


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

Розум [0].

Це правильно використовувати reduceяк вAntonio Laguna відповіді.

Вибачення за стислість ...


4

Якщо ваш об’єкт є тим самим об'єктом, який ви використовуєте в масиві, ви повинні мати можливість отримати індекс Об'єкта так само, як і ви, як якщо б це був рядок.

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

Це відповідь, яку я шукав, оскільки мені було незрозуміло, чи відповідає IndexOf за значенням чи чим. Тепер я знаю, що можу використовувати IndexOf для пошуку свого об’єкта, і не хвилююся, чи є інші об'єкти з тими ж властивостями.
MDave



3

Якщо вас цікавить лише пошук посади, дивіться відповідь @ Пабло .

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

Однак якщо ви з нетерпінням чекаєте пошуку елемента (тобто якщо ви думали зробити щось подібне myArray[pos]), існує більш ефективний однолінійний спосіб зробити це, використовуючи filter.

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

Дивіться результати роботи (~ + 42% ops / sec): http://jsbench.github.io/#7fa01f89a5dc5cc3bee79abfde80cdb3



2

Я зробив загальну функцію, щоб перевірити, чи наведено нижче код і працює для будь-якого об'єкта

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

Перше сповіщення повернеться -1 (означає, що збіг не знайдено), а друге сповіщення поверне 0 (означає, що збіг знайдено).


2

Використовуйте _.findIndexз бібліотеки underscore.js

Ось приклад _.findIndex([{a:1},{a: 2,c:10},{a: 3}], {a:2,c:10}) //1


Якщо ви пропонуєте методи з додаткових бібліотек, вам слід зазначити, звідки вони походять.
Craicerjack

@Steve Bennett приємно користуватися. Бібліотека зараз її в квартирі
16:00

2

Використовуючи findIndexметод ES6 , без lodash або будь-яких інших бібліотек, ви можете писати:

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

Це дозволить порівняти безпосередні властивості об’єкта, але не повторити їх властивості.

Якщо ваша реалізація ще не надає findIndex(більшість ні), ви можете додати легкий поліфайл, який підтримує цей пошук:

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

(з моєї відповіді на цю дупу )


2

Ви можете використовувати рідну та зручну функцію в Array.prototype.findIndex()основному:

Метод findIndex () повертає індекс у масиві, якщо елемент масиву задовольняє надану функцію тестування. Інакше -1 повертається.

Просто зауважте, що він не підтримується в Internet Explorer, Opera та Safari, але ви можете використовувати Polyfill, наданий за посиланням нижче.

Більше інформації:

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


2

Furthor з @Monika Garg відповіді , ви можете використовувати findIndex()(Існує polyfill для unsupprted браузерів).

Я бачив, що люди спростували цю відповідь, і сподіваюся, що вони зробили це через неправильний синтаксис, бо, на мою думку, це найелегантніший спосіб.

Метод findIndex () повертає індекс у масиві, якщо елемент масиву задовольняє надану функцію тестування. Інакше -1 повертається.

Наприклад:

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


Метод здається елегантним, але немає підтримки IE згідно MDN? developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
Jaakko Karhu

Спробуйте скористатися поліфіл (посилання у відповіді).
Мош Феу

1

Це спосіб знайти індекс об'єкта в масиві

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

0

Це працює без спеціального коду

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

Примітка. Це не дає фактичного індексу, воно лише повідомляє, чи існує ваш об'єкт у поточній структурі даних


1
Це неправда, оскільки це не дозволяє отримати індекс
Антоніо Лагуна

0

Ви можете просто використовувати

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

Ні підкреслення, ні для, а лише зменшення.


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

Використовуйте Array якийсь метод.
користувач3235365

-1

Ви можете створити власний прототип для цього:

щось на зразок:

Array.prototype.indexOfObject = function (object) {
    for (var i = 0; i < this.length; i++) {
        if (JSON.stringify(this[i]) === JSON.stringify(object))
            return i;
    }
}

2
Погана практика, порушення інкапсуляції: developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/…
HMR

Це також порушить рекурсивно визначені об'єкти.
Джозеф Коко

-2

Я вважаю за краще використовувати findIndex()метод:

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

index дасть вам номер індексу.


1
Відповідь, правопис та відступ коду та :) помиляються?
Сахін Верма

1
findIndex не міститься в жодній стандартній реалізації JavaScript. Існує майбутня (ecma 6) пропозиція щодо такого методу, але його підпис не такий. Поясніть, будь ласка, що ви маєте на увазі (можливо, назва методу), дайте декларацію методу findIndex або назвіть бібліотеку, яку ви використовуєте.
Себастьєн Ф.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.