为什么Array.indexOf无法找到外观相同的对象

53

我有一个包含对象的数组。

大概长这样:

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

当我尝试执行以下操作:

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

它返回-1

如果我有字符串或数字或其他类型的元素,但不是对象,则 indexOf()运行良好。

有人知道为什么以及我应该如何搜索数组中的对象元素吗?

当然,我是指除了为对象创建字符串哈希键并将其放入数组之外的方法...


为什么不能使用哈希函数呢?我想知道原因是什么? - raina77ow
9
请注意,根据定义,即使两个对象具有完全相同的属性名称和值,它们也永远不会相等。只有当objectAobjectB引用同一对象时,objectA === objectB才成立。 - RobG
规格说明对于字符串没有这样的说明:如果它们都是字符串并且具有相同的字符,则它们是相等的。 - Denys Séguret
@RobG 我不明白你的观点。也许你应该看一下这个链接(http://jsfiddle.net/dystroy/ert8G/),如果有什么不清楚的地方。你会明白为什么我在字符串方面对你进行了修正。 - Denys Séguret
@dystroy - MDN不是规范,而是一个社区维基。链接中所述的“标准相等运算符(==和!=)比较两个操作数而不考虑它们的类型”是错误的,请参见抽象相等比较算法的第一步。 - RobG
显示剩余5条评论
8个回答

48

indexOf使用严格相等运算符(与===或三个等号运算符使用的相同方法)将searchElement与Array的元素进行比较。

你不能使用===来检查对象的可等性。

正如@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;
}

示例: http://jsfiddle.net/zQtML/


1
这意味着我无法比较两个对象是否相同? - Jibla
1
你可以使用 === 来检查对象的相等性,但当这两个对象是完全不同的字面量时,它将会失败。 - jbabey
@Jibla 写一个简单的函数来迭代对象并查找匹配项。请检查更新的帖子。 - Selvakumar Arumugam
@jbaby - 无论你使用 == 还是 ===,两个对象永远不会相等。无论是 抽象 还是 严格 相等比较算法(以及关系运算符)都被定义为如果被比较的表达式是对象,则返回 false。 - RobG
@vega,这不是通用解决方案,它只适用于原帖作者。 - RobG
显示剩余3条评论

17

没有人提到内置函数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


最佳答案并不需要编写新的函数! - JesseBoyd

12

如前所述,两个对象永远不会相等,但如果它们指向同一个对象,则引用可以相等,因此为了让代码做你想要的事情:

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

是的,但我动态生成要搜索的对象。谢谢。 - Jibla
然后,您将需要迭代属性并比较值。您还必须双向比较,即从a到b和从b到a。 - RobG
为什么要这样写?我写了这样的函数: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
如果您恰好拥有这些属性名称,那么这将起作用。顺便说一句,在数组上使用for..in不是推荐的做法,特别是随着“猴子补丁”在Array.prototpye上添加可枚举属性的ES5功能的普及。使用带有数字索引的普通for循环,然后您只需返回i,而无需将其转换为数字。 - RobG

11

这可以在没有自定义代码的情况下工作

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
恭喜,你仍然比一些库更快。 - Vishal Kumar Sahu

3

这些对象不相等。

您必须实现自己的函数。

例如,您可以这样做:

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

其中searched是您的对象之一(或不是)。

(我会用简单的循环来实现它,但使用foreach更美观)


3
因为两个独立的对象彼此不等于(使用 === ),而 indexOf 使用 === 。(它们也不相等于 == 。)
示例:

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

=====用于测试它们的操作数是否引用相同的对象,而不是引用等效的对象(具有相同原型和属性的对象)。


0
这是另一种解决方案,您可以将比较函数作为参数传递:
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);

0

看起来你对这种类型的答案不感兴趣,但对于其他有兴趣的人来说,这是最简单的答案:

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 + ")"
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接