53

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...

Jibla
  • 813
  • 2
  • 8
  • 12
  • And why making hashes is not an option, I wonder? – raina77ow Sep 26 '12 at 14:35
  • 9
    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. – RobG Sep 26 '12 at 14:38
  • The spec doesn't say so for strings : they're equal if they're both strings and have the same characters. – Denys Séguret Sep 26 '12 at 14:39
  • I agree with === comparator, but why it doesn't work with == this comparator I dont understand... – Jibla Sep 26 '12 at 14:43
  • @dystroy—if you use string objects (and the OP is talking about objets), they are never equal because they are Type Object, not String. – RobG Sep 26 '12 at 15:08
  • @RobG I don't see your point. Maybe you should look at [this](http://jsfiddle.net/dystroy/ert8G/) is something is not clear for you. You'll see why I was correcting you regarding strings. – Denys Séguret Sep 26 '12 at 15:09
  • @jbabey Maybe I'm wrong but the questions you link to don't seem to be related to the problem OP has here (check of equality defined by properties equality). – Denys Séguret Sep 26 '12 at 15:23
  • @dystroy there are string **primitive** values and String **object** instances; it's like the difference between numbers and Number instances. Comparison between String object instances with `===` also are based on object identity and not string value. – Pointy Sep 26 '12 at 17:06
  • https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/Comparison_Operators – Denys Séguret Sep 26 '12 at 17:12
  • @dystroy—MDN is not the specification, it's a community wiki. The statement made in the link that the "standard equality operators (== and !=) compare two operands without regard to their type" is wrong, see step 1 of the [the Abstract Equality Comparison Algorithm](http://ecma-international.org/ecma-262/5.1/#sec-11.9.3). – RobG Sep 27 '12 at 00:49

8 Answers8

48

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/

Community
  • 1
  • 1
Selvakumar Arumugam
  • 79,297
  • 15
  • 120
  • 134
  • 1
    This means, I cant compare whether two objects are identical or not? – Jibla Sep 26 '12 at 14:36
  • 1
    You can use `===` to check equality of objects, just expect it to fail when those two objects are completely different literals. – jbabey Sep 26 '12 at 14:38
  • @Jibla Write a simple function to iterate over the object and find the match.. Check updated post. – Selvakumar Arumugam Sep 26 '12 at 14:40
  • @Vega - Yes, I know the least option to write custom function, I just didn't want to write custom one. P.S. Thank you! – Jibla Sep 26 '12 at 14:45
  • @jbaby—two objects are never equal, regardless of whether you use `==` or `===`. Both the [abstract](http://ecma-international.org/ecma-262/5.1/#sec-11.9.3) and [strict](http://ecma-international.org/ecma-262/5.1/#sec-11.9.6) equality comparison algorithms (and the relational ones) are defined as returning false if the expressions being compared are objects. – RobG Sep 26 '12 at 14:49
  • @vega—that is not a general solution, it is specific to the OP. – RobG Sep 26 '12 at 15:12
  • @RobG Is it supposed to be specific to OP? We can do a `for..in` comparision.. but it would be a overhead here. – Selvakumar Arumugam Sep 26 '12 at 15:13
  • Nice method for when jQuery `inArray()` is not available. I needed this function for sub-object search which took a slight [modification](http://jsfiddle.net/zQtML/10/). – Brandon Clark Dec 07 '14 at 20:25
17

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

Matt
  • 225
  • 3
  • 3
12

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
RobG
  • 142,382
  • 31
  • 172
  • 209
  • yes, but I generate dynamically the object which I search. Thanks. – Jibla Sep 26 '12 at 14:52
  • Then you will have to iterate over the properties and compare values. You also have to compare both ways, i.e. a to be and b to a. – RobG Sep 26 '12 at 15:09
  • Why both ways? I wrote function like this: availableMoves.indexOf = function(obj) { for(i in this) { if (this[i].x == obj.x && this[i].y == obj.y) { return parseInt(i); } } return -1; } – Jibla Sep 26 '12 at 15:12
  • That will work if you have exactly those property names. Incidentally, using for..in over an array is not recommended, particularly with the popularity of "monkey patches" for `Array.prototpye` to add ES5 features that add enumerable properties. Use a normal `for` loop with a numeric index, then you can just return `i` and don't need to convert it to a number. – RobG Sep 26 '12 at 15:35
11

This works without custom code

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

Note: this does not give the actual index, it only tells if your object exists in the current data structure

Xeltor
  • 4,626
  • 3
  • 24
  • 26
3

Those objects aren't equal.

You must implement your own function.

You may do that for example :

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

where searched is one of your object (or not).

(I would implement it with a simple loop but it's prettier with foreach)

Denys Séguret
  • 372,613
  • 87
  • 782
  • 758
3

Because two separate objects are not === to each other, and indexOf uses ===. (They're also not == to each other.)

Example:

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

=== and == test for whether their operands refer to the same object, not if they refer to equivalent objects (objects with the same prototype and properties).

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
0

Here's another solution, where you pass a compare function as a parameter :

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

You can then use it these way:

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

The good thing is this still calls the original indexOf if no compare function is passed.

[1,2,3,4].indexOf(3);
Kristian Benoit
  • 612
  • 5
  • 16
0

Looks like you weren't interested in this type of answer, but it is the simplest to make for others who are interested:

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 + ")"
}
Dal
  • 165
  • 10