Why Array.indexOf doesn't find identical looking objects

独自空忆成欢 提交于 2019-11-26 13:22:40
Selvakumar Arumugam

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/

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

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

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)

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

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

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

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 + ")"
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!