Why Array.indexOf doesn't find identical looking objects

I have array with objects.

Something Like this:

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

When I try:

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

It returns -1.

If I have strings or numbers or other type of elements but object, then indexOf() works fine.

Does anyone know why and what should I do to search object elements in array?

Of course, I mean the ways except making string hash keys for objects and give it to array...


Solution 1:

indexOf compares searchElement to elements of the Array using strict equality (the same method used by the ===, or triple-equals, operator).

You cannot use === to check the equability of an object.

As @RobG pointed out

Note that by definition, two objects are never equal, even if they have exactly the same property names and values. objectA === objectB if and only if objectA and objectB reference the same object.

You can simply write a custom indexOf function to check the object.

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/

Solution 2:

As nobody has mentioned built-in function Array.prototype.findIndex(), I'd like to mention that it does exactly what author needs.

The findIndex() method returns the index of the first element in the array that satisfies the provided testing function. Otherwise -1 is returned.

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

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

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

In your case it would be:

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

Or using ES6

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

More information with the example above: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex

Solution 3:

As noted, two objects are never equal, but references can be equal if they are to the same object, so to make the code do what you want:

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

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

Edit

Here's a more general specialIndexOf function. Note that it expects the values of the objects to be primitives, otherwise it needs to be more rigorous.

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