Подтвердить что ты не робот

Почему Array.indexOf не находит одинаково выглядящие объекты

У меня есть массив с объектами.

Что-то вроде этого:

var arr = new Array(
  {x:1, y:2},
  {x:3, y:4}
);

Когда я пытаюсь:

arr.indexOf({x:1, y:2});

Возвращает -1.

Если у меня есть строки или числа или другие типы элементов, но объект, то indexOf() отлично работает.

Кто-нибудь знает, почему и что мне делать, чтобы искать элементы объекта в массиве?

Конечно, я имею в виду пути, кроме создания строковых хеш-ключей для объектов и передачи их в массив...

4b9b3361

Ответ 1

indexOf сравнивает searchElement с элементами массива с использованием строгого равенства (тот же метод, который используется оператором === или triple-equals).

Вы не можете использовать === для проверки равноценности объекта.

Как @RobG указал

Обратите внимание, что по определению два объекта никогда не равны, даже если они имеют одинаковые имена и значения свойств. objectA === objectB тогда и только тогда, когда objectA и objectB ссылаются на один и тот же объект.

Вы можете просто написать пользовательскую функцию indexOf для проверки объекта.

function myIndexOf(o) {    
    for (var i = 0; i < arr.length; i++) {
        if (arr[i].x == o.x && arr[i].y == o.y) {
            return i;
        }
    }
    return -1;
}

DEMO: http://jsfiddle.net/zQtML/

Ответ 2

Как уже отмечалось, два объекта никогда не равны, но ссылки могут быть равны, если они относятся к одному и тому же объекту, поэтому чтобы код делал то, что вам нужно:

var a = {x:1, y:2};
var b = {x:3, y:4};
var arr = [a, b];

alert(arr.indexOf(a)); // 0

Изменить

Здесь используется более общая функция specialIndexOf. Обратите внимание, что он ожидает, что значения объектов будут примитивными, в противном случае это должно быть более строгим.

function specialIndexOf(arr, value) {
  var a;
  for (var i=0, iLen=arr.length; i<iLen; i++) {
    a = arr[i];

    if (a === value) return i;

    if (typeof a == 'object') {
      if (compareObj(arr[i], value)) {
        return i;
      }
    } else {
      // deal with other types
    }
  }
  return -1;

  // Extremely simple function, expects the values of all 
  // enumerable properties of both objects to be primitives.
  function compareObj(o1, o2, cease) {
    var p;

    if (typeof o1 == 'object' && typeof o2 == 'object') {

      for (p in o1) {
        if (o1[p] != o2[p]) return false; 
      }

      if (cease !== true) {
        compareObj(o2, o1, true);
      }

      return true;
    }
  }
}

var a = new String('fred');
var b = new String('fred');

var arr = [0,1,a];

alert(specialIndexOf(arr, b)); // 2

Ответ 3

Это работает без специального кода

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

Примечание: это не дает фактического индекса, он сообщает только, существует ли ваш объект в текущей структуре данных

Ответ 4

Поскольку никто не упомянул встроенную функцию Array.prototype.findIndex(), я хотел бы отметить, что она делает именно то, что нужно автору.

Метод findIndex() возвращает индекс первого элемента в массив, который удовлетворяет предоставленной функции тестирования. В противном случае -1 вернулся.

var array1 = [5, 12, 8, 130, 44];

function findFirstLargeNumber(element) {
  return element > 13;
}

console.log(array1.findIndex(findFirstLargeNumber));
// expected output: 3

В вашем случае это будет:

arr.findIndex(function(element) {
 return element.x == 1 && element.y == 2;
});

Или используя ES6

arr.findIndex( element => element.x == 1 && element.y == 2 );

Дополнительная информация на примере выше: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex

Ответ 5

Эти объекты не равны.

Вы должны реализовать свою собственную функцию.

Вы можете сделать это, например:

var index = -1;
arr.forEach(function(v, i) {
   if (this.x==v.x && this.y==v.y) index=i;
}, searched); 

где searched является одним из ваших объектов (или нет).

(я бы выполнил его с помощью простого цикла, но он красивее foreach)

Ответ 6

Поскольку два отдельных объекта не являются === друг для друга, а indexOf использует ===. (Они также не == друг к другу.)

Пример:

var a = {x:1, y:2};
var b = {x:1, y:2};
console.log(a === b);

Ответ 7

Здесь другое решение, в котором вы передаете функцию сравнения в качестве параметра:

function indexOf(array, val, from, compare) {

  if (!compare) {
    if (from instanceof Function) {
      compare = from;
      from = 0;
    }
    else return array.__origIndexOf(val, from);
  }

  if (!from) from = 0;

  for (var i=from ; i < array.length ; i++) {
    if (compare(array[i], val))
      return i;
  }
  return -1;
}

// Save original indexOf to keep the original behaviour
Array.prototype.__origIndexOf = Array.prototype.indexOf;

// Redefine the Array.indexOf to support a compare function.
Array.prototype.indexOf = function(val, from, compare) {
  return indexOf(this, val, from, compare);
}

Затем вы можете использовать его следующим образом:

indexOf(arr, {x:1, y:2}, function (a,b) {
 return a.x == b.x && a.y == b.y;
});

arr.indexOf({x:1, y:2}, function (a,b) {
 return a.x == b.x && a.y == b.y;
});

arr.indexOf({x:1, y:2}, 1, function (a,b) {
 return a.x == b.x && a.y == b.y;
});

Хорошо, что это все еще вызывает исходный indexOf, если функция сравнения не передана.

[1,2,3,4].indexOf(3);

Ответ 8

Похоже, вы не были заинтересованы в этом типе ответа, но это проще всего сделать для других, заинтересованных:

var arr = new Array(
    {x:1, y:2},
    {x:3, y:4}
);

arr.map(function(obj) {
    return objStr(obj);
}).indexOf(objStr({x:1, y:2}));

function objStr(obj) {
    return "(" + obj.x + ", " + obj.y + ")"
}