¿Cómo puedo determinar la igualdad de dos objetos JavaScript?

Resuelto asked hace 15 años • 77 respuestas

Un operador de igualdad estricta le dirá si dos tipos de objetos son iguales. Sin embargo, ¿hay alguna manera de saber si dos objetos son iguales, de manera similar al valor del código hash en Java?

Pregunta de desbordamiento de pila ¿Existe algún tipo de función hashCode en JavaScript? Es similar a esta pregunta, pero requiere una respuesta más académica. El escenario anterior demuestra por qué sería necesario tener uno y me pregunto si existe alguna solución equivalente .

 avatar Oct 14 '08 20:10
Aceptado

¿Por qué reinventar la rueda? Prueba Lodash . Tiene una serie de funciones imprescindibles como isEqual() .

_.isEqual(object, other);

Verificará con fuerza bruta cada valor clave, al igual que los otros ejemplos en esta página, usando ECMAScript 5 y optimizaciones nativas si están disponibles en el navegador.

Nota: Anteriormente, esta respuesta recomendaba Underscore.js , pero lodash ha hecho un mejor trabajo al corregir errores y abordar problemas con coherencia.

coolaj86 avatar Jul 07 '2010 19:07 coolaj86

La respuesta corta

La respuesta simple es: No, no existe un medio genérico para determinar que un objeto es igual a otro en el sentido que usted quiere decir. La excepción es cuando se piensa estrictamente en que un objeto no tiene tipo.

la respuesta larga

El concepto es el de un método Equals que compara dos instancias diferentes de un objeto para indicar si son iguales a nivel de valor. Sin embargo, depende del tipo específico definir cómo Equalsse debe implementar un método. Una comparación iterativa de atributos que tienen valores primitivos puede no ser suficiente: un objeto puede contener atributos que no son relevantes para la igualdad. Por ejemplo,

 function MyClass(a, b)
 {
     var c;
     this.getCLazy = function() {
         if (c === undefined) c = a * b // imagine * is really expensive
         return c;
     }
  }

En el caso anterior, cno es realmente importante determinar si dos instancias cualesquiera de MyClass son iguales, sólo ason bimportantes. En algunos casos c, puede variar entre casos y aún así no ser significativo durante la comparación.

Tenga en cuenta que este problema se aplica cuando los miembros también pueden ser instancias de un tipo y cada uno de ellos debería tener un medio para determinar la igualdad.

Para complicar aún más las cosas, en JavaScript la distinción entre datos y métodos es borrosa.

Un objeto puede hacer referencia a un método que se llamará como controlador de eventos, y esto probablemente no se considerará parte de su "estado de valor". Mientras que a otro objeto bien se le puede asignar una función que realiza un cálculo importante y, por lo tanto, hace que esta instancia sea diferente de otras simplemente porque hace referencia a una función diferente.

¿Qué pasa con un objeto que tiene uno de sus métodos prototipo existentes anulado por otra función? ¿Podría considerarse igual a otro caso que por lo demás es idéntico? Esa pregunta sólo puede responderse en cada caso concreto de cada tipo.

Como se indicó anteriormente, la excepción sería un objeto estrictamente sin tipo. En cuyo caso la única opción sensata es una comparación iterativa y recursiva de cada miembro. Incluso entonces uno tiene que preguntarse ¿cuál es el "valor" de una función?

AnthonyWJones avatar Oct 14 '2008 14:10 AnthonyWJones

El operador de igualdad predeterminado en JavaScript para objetos produce verdadero cuando se refieren a la misma ubicación en la memoria.

var x = {};
var y = {};
var z = x;

x === y; // => false
x === z; // => true

Si necesita un operador de igualdad diferente, deberá agregar un equals(other)método o algo similar a sus clases y los detalles del dominio de su problema determinarán qué significa exactamente eso.

Aquí hay un ejemplo de naipes:

function Card(rank, suit) {
  this.rank = rank;
  this.suit = suit;
  this.equals = function(other) {
     return other.rank == this.rank && other.suit == this.suit;
  };
}

var queenOfClubs = new Card(12, "C");
var kingOfSpades = new Card(13, "S");

queenOfClubs.equals(kingOfSpades); // => false
kingOfSpades.equals(new Card(13, "S")); // => true
Daniel X Moore avatar May 20 '2009 03:05 Daniel X Moore

Implementación funcional breve deepEqual:

function deepEqual(x, y) {
  return (x && y && typeof x === 'object' && typeof y === 'object') ?
    (Object.keys(x).length === Object.keys(y).length) &&
      Object.keys(x).reduce(function(isEqual, key) {
        return isEqual && deepEqual(x[key], y[key]);
      }, true) : (x === y);
}

Editar : versión 2, usando la sugerencia de jib y las funciones de flecha ES6:

function deepEqual(x, y) {
  const ok = Object.keys, tx = typeof x, ty = typeof y;
  return x && y && tx === 'object' && tx === ty ? (
    ok(x).length === ok(y).length &&
      ok(x).every(key => deepEqual(x[key], y[key]))
  ) : (x === y);
}
atmin avatar Oct 03 '2015 11:10 atmin

Esta es mi versión. Está utilizando la nueva característica Object.keys que se introduce en ES5 e ideas/pruebas de + , + y + :

function objectEquals(x, y) {
    'use strict';

    if (x === null || x === undefined || y === null || y === undefined) { return x === y; }
    // after this just checking type of one would be enough
    if (x.constructor !== y.constructor) { return false; }
    // if they are functions, they should exactly refer to same one (because of closures)
    if (x instanceof Function) { return x === y; }
    // if they are regexps, they should exactly refer to same one (it is hard to better equality check on current ES)
    if (x instanceof RegExp) { return x === y; }
    if (x === y || x.valueOf() === y.valueOf()) { return true; }
    if (Array.isArray(x) && x.length !== y.length) { return false; }

    // if they are dates, they must had equal valueOf
    if (x instanceof Date) { return false; }

    // if they are strictly equal, they both need to be object at least
    if (!(x instanceof Object)) { return false; }
    if (!(y instanceof Object)) { return false; }

    // recursive object equality check
    var p = Object.keys(x);
    return Object.keys(y).every(function (i) { return p.indexOf(i) !== -1; }) &&
        p.every(function (i) { return objectEquals(x[i], y[i]); });
}


///////////////////////////////////////////////////////////////
/// The borrowed tests, run them by clicking "Run code snippet"
///////////////////////////////////////////////////////////////
var printResult = function (x) {
    if (x) { document.write('<div style="color: green;">Passed</div>'); }
    else { document.write('<div style="color: red;">Failed</div>'); }
};
var assert = { isTrue: function (x) { printResult(x); }, isFalse: function (x) { printResult(!x); } }
assert.isTrue(objectEquals(null,null));
assert.isFalse(objectEquals(null,undefined));
assert.isFalse(objectEquals(/abc/, /abc/));
assert.isFalse(objectEquals(/abc/, /123/));
var r = /abc/;
assert.isTrue(objectEquals(r, r));

assert.isTrue(objectEquals("hi","hi"));
assert.isTrue(objectEquals(5,5));
assert.isFalse(objectEquals(5,10));

assert.isTrue(objectEquals([],[]));
assert.isTrue(objectEquals([1,2],[1,2]));
assert.isFalse(objectEquals([1,2],[2,1]));
assert.isFalse(objectEquals([1,2],[1,2,3]));

assert.isTrue(objectEquals({},{}));
assert.isTrue(objectEquals({a:1,b:2},{a:1,b:2}));
assert.isTrue(objectEquals({a:1,b:2},{b:2,a:1}));
assert.isFalse(objectEquals({a:1,b:2},{a:1,b:3}));

assert.isTrue(objectEquals({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}},{1:{name:"mhc",age:28}, 2:{name:"arb",age:26}}));
assert.isFalse(objectEquals({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}},{1:{name:"mhc",age:28}, 2:{name:"arb",age:27}}));

Object.prototype.equals = function (obj) { return objectEquals(this, obj); };
var assertFalse = assert.isFalse,
    assertTrue = assert.isTrue;

assertFalse({}.equals(null));
assertFalse({}.equals(undefined));

assertTrue("hi".equals("hi"));
assertTrue(new Number(5).equals(5));
assertFalse(new Number(5).equals(10));
assertFalse(new Number(1).equals("1"));

assertTrue([].equals([]));
assertTrue([1,2].equals([1,2]));
assertFalse([1,2].equals([2,1]));
assertFalse([1,2].equals([1,2,3]));
assertTrue(new Date("2011-03-31").equals(new Date("2011-03-31")));
assertFalse(new Date("2011-03-31").equals(new Date("1970-01-01")));

assertTrue({}.equals({}));
assertTrue({a:1,b:2}.equals({a:1,b:2}));
assertTrue({a:1,b:2}.equals({b:2,a:1}));
assertFalse({a:1,b:2}.equals({a:1,b:3}));

assertTrue({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}}.equals({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}}));
assertFalse({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}}.equals({1:{name:"mhc",age:28}, 2:{name:"arb",age:27}}));

var a = {a: 'text', b:[0,1]};
var b = {a: 'text', b:[0,1]};
var c = {a: 'text', b: 0};
var d = {a: 'text', b: false};
var e = {a: 'text', b:[1,0]};
var i = {
    a: 'text',
    c: {
        b: [1, 0]
    }
};
var j = {
    a: 'text',
    c: {
        b: [1, 0]
    }
};
var k = {a: 'text', b: null};
var l = {a: 'text', b: undefined};

assertTrue(a.equals(b));
assertFalse(a.equals(c));
assertFalse(c.equals(d));
assertFalse(a.equals(e));
assertTrue(i.equals(j));
assertFalse(d.equals(k));
assertFalse(k.equals(l));

// from comments on stackoverflow post
assert.isFalse(objectEquals([1, 2, undefined], [1, 2]));
assert.isFalse(objectEquals([1, 2, 3], { 0: 1, 1: 2, 2: 3 }));
assert.isFalse(objectEquals(new Date(1234), 1234));

// no two different function is equal really, they capture their context variables
// so even if they have same toString(), they won't have same functionality
var func = function (x) { return true; };
var func2 = function (x) { return true; };
assert.isTrue(objectEquals(func, func));
assert.isFalse(objectEquals(func, func2));
assert.isTrue(objectEquals({ a: { b: func } }, { a: { b: func } }));
assert.isFalse(objectEquals({ a: { b: func } }, { a: { b: func2 } }));
Expandir fragmento

Ebrahim Byagowi avatar May 28 '2013 09:05 Ebrahim Byagowi