¿Cómo determinar la igualdad para dos objetos JavaScript?

670

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, como el valor del código hash en Java?

Pregunta de desbordamiento de pila ¿Hay 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 hay una solución equivalente .

Comunidad
fuente
3
También mire esta pregunta stackoverflow.com/q/1068834/1671639
Praveen
37
Tenga en cuenta que, incluso en Java, a.hashCode() == b.hashCode()no , no implica que asea igual b. Es una condición necesaria, no suficiente.
Heinzi
Por
favor
2
Si TIENE que comparar objetos en su código, entonces probablemente esté escribiendo su código incorrectamente. La mejor pregunta podría ser: "¿Cómo puedo escribir este código para no tener que comparar objetos?"
th317erd
3
@ th317erd, ¿puedes explicarte? ...
El Mac

Respuestas:

175

La respuesta corta

La respuesta simple es: No, no hay medios genéricos para determinar que un objeto es igual a otro en el sentido que quiere decir. La excepción es cuando está estrictamente pensando 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 en un nivel de valor. Sin embargo, depende del tipo específico definir cómo unEquals se debe implementar método. Una comparación iterativa de atributos que tienen valores primitivos puede no ser suficiente, bien puede haber atributos que no deben considerarse parte del valor del objeto. Por ejemplo,

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

En este caso anterior, cno es realmente importante determinar si dos instancias de MyClass son iguales, soloa y si bson importantes. En algunos casos, cpuede variar entre instancias y, sin embargo, 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.

Lo que complica aún más las cosas es que en JavaScript la distinción entre datos y método es borrosa.

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

¿Qué pasa con un objeto que tiene uno de sus métodos prototipo existentes anulados por otra función? ¿Podría aún considerarse igual a otra instancia que de lo contrario es idéntico? Esa pregunta solo puede responderse en cada caso específico para 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
fuente
176
Si está usando guión bajo, puede hacerlo_.isEqual(obj1, obj2);
chovy
12
@ Harsh, la respuesta no pudo dar ninguna solución porque no hay ninguna. Incluso en Java, no hay una bala de plata para la comparación de igualdad de objetos y para implementar correctamente el .equalsmétodo no es trivial, por lo que existe un tema dedicado en Java efectivo .
lcn
3
@ Kumar Harsh, lo que hace que dos objetos sean iguales es muy específico de la aplicación; no todas las propiedades de un objeto necesariamente deben tenerse en cuenta, por lo que forzar a cada propiedad bruta de un objeto tampoco es una solución concreta.
sethro
busqué en Google javascript equality object, obtuve tl; la respuesta del Dr., tomó una frase del comentario de @chovy. gracias
Andrea
Si usa angular, tieneangular.equals
barcocoder
509

¿Por qué reinventar la rueda? Dale una oportunidad a Lodash . Tiene varias funciones imprescindibles , como isEqual () .

_.isEqual(object, other);

Verificará la fuerza bruta de cada valor clave, al igual que los otros ejemplos en esta página, utilizando 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 los errores y abordar los problemas con coherencia.

CoolAJ86
fuente
27
La función isEqual de Underscore es muy agradable (pero debe extraer su biblioteca para usarla, aproximadamente 3K comprimidos).
mckoss
29
si miras lo que te da el guión bajo, no te arrepentirás de haberlo
introducido
66
Incluso si no puede permitirse tener un guión bajo como dependencia, elimine la función isEqual, satisfaga los requisitos de licencia y continúe. Es, con mucho, la prueba de igualdad más completa mencionada en stackoverflow.
Dale Anderson
77
Hay una bifurcación de Underscore llamada LoDash y ese autor está muy preocupado con problemas de coherencia como ese. Pruebe con LoDash y vea lo que obtiene.
CoolAJ86
66
@mckoss puede usar el módulo independiente si no desea toda la biblioteca npmjs.com/package/lodash.isequal
Rob Fox
161

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
fuente
Si los objetos se pueden convertir en una cadena JSON, entonces hace que la función equals () sea simple.
scotts
3
@scotts No siempre. La conversión de objetos a JSON y la comparación de cadenas pueden volverse computacionalmente intensivas para objetos complejos en bucles cerrados. Para objetos simples, probablemente no importa mucho, pero en realidad realmente depende de su situación específica. Una solución correcta puede ser tan simple como comparar ID de objetos o verificar cada propiedad, pero su corrección está determinada completamente por el dominio del problema.
Daniel X Moore el
¿No deberíamos comparar el tipo de datos también? devuelve other.rank === this.rank && other.suit === this.suit;
devsathish
1
@devsathish probablemente no. En JavaScript, los tipos son bastante rápidos y sueltos, pero si en su dominio los tipos son importantes, es posible que también desee verificarlos.
Daniel X Moore
77
@scotts Otro problema con la conversión a JSON es que el orden de las propiedades en la cadena se vuelve significativo. {x:1, y:2}! =={y:2, x:1}
Stijn de Witt
81

Si está trabajando en AngularJS , la angular.equalsfunción determinará si dos objetos son iguales. En uso de Ember.jsisEqual .

  • angular.equals- Consulte los documentos o la fuente para obtener más información sobre este método. También hace una comparación profunda en matrices.
  • Ember.js isEqual: consulte los documentos o la fuente para obtener más información sobre este método. No hace una comparación profunda en las matrices.

var purple = [{"purple": "drank"}];
var drank = [{"purple": "drank"}];

if(angular.equals(purple, drank)) {
    document.write('got dat');
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js"></script>

Troy Harvey
fuente
66

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

Ebrahim Byagowi
fuente
objectEquals([1,2,undefined],[1,2])regresatrue
Roy Tinker
objectEquals([1,2,3],{0:1,1:2,2:3})también regresa true, por ejemplo, no hay verificación de tipo, solo verificación de clave / valor.
Roy Tinker
objectEquals(new Date(1234),1234)regresatrue
Roy Tinker
1
if (x.constructor! == y.constructor) {return false; } Esto se rompería al comparar dos 'cadenas nuevas (' a ')' en ventanas diferentes. Para la igualdad de valores, tendría que verificar si String.isString en ambos objetos, luego usar una verificación de igualdad suelta 'a == b'.
Triynko
1
Hay una gran diferencia entre la igualdad de "valor" y la igualdad "estricta" y no deberían implementarse de la misma manera. La igualdad de valores no debería preocuparse por los tipos, aparte de la estructura básica, que es uno de estos 4: 'objeto' (es decir, una colección de pares clave / valor), 'número', 'cadena' o 'matriz'. Eso es. Cualquier cosa que no sea un número, cadena o matriz, debe compararse como un conjunto de pares clave / valor, independientemente de cuál sea el constructor (cross-window-safe). Al comparar objetos, equipare el valor de los números literales y las instancias de Number, pero no fuerce las cadenas a los números.
Triynko
50

Si está utilizando una biblioteca JSON, puede codificar cada objeto como JSON y luego comparar las cadenas resultantes para la igualdad.

var obj1={test:"value"};
var obj2={test:"value2"};

alert(JSON.encode(obj1)===JSON.encode(obj2));

NOTA: Si bien esta respuesta funcionará en muchos casos, como varias personas han señalado en los comentarios, es problemática por varias razones. En casi todos los casos, querrás encontrar una solución más robusta.

Joel Anair
fuente
9191
Interesante, pero un poco complicado en mi opinión. Por ejemplo, ¿puede garantizar al 100% que las propiedades del objeto se generarán siempre en el mismo orden?
Guido
25
Esa es una buena pregunta, y plantea otra, en cuanto a si dos objetos con las mismas propiedades en diferentes órdenes son realmente iguales o no. Depende de lo que quieras decir con igual, supongo.
Joel Anair
11
Tenga en cuenta que la mayoría de los codificadores y encadenadores ignoran las funciones y convierten los números no finitos, como NaN, en nulos.
Stephen Belanger
44
Estoy de acuerdo con Guido, el orden de las propiedades es importante y no se puede garantizar. @JoelAnair, creo que dos objetos con las mismas propiedades en diferentes órdenes deberían considerarse iguales si el valor de las propiedades es igual.
Juzer Ali
55
Esto podría funcionar con un stringifier JSON alternativo, uno que clasifique las claves de objeto de manera consistente.
Roy Tinker
40

deepEqualImplementación funcional corta :

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

Edición : versión 2, utilizando 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
fuente
55
Podría reemplazar reducecon everypara simplificar.
foque
1
@nonkertompf seguro de poder: Object.keys(x).every(key => deepEqual(x[key], y[key])).
foque
2
Esto falla cuando se comparan dos fechas
Greg
3
deepEqual ({}, []) devuelve verdadero
AlexMorley-Finch
3
sí, si te importa ese caso de esquina, la solución fea es reemplazar : (x === y)con: (x === y && (x != null && y != null || x.constructor === y.constructor))
atmin
22

¿Estás tratando de probar si dos objetos son iguales? es decir: sus propiedades son iguales?

Si este es el caso, probablemente habrás notado esta situación:

var a = { foo : "bar" };
var b = { foo : "bar" };
alert (a == b ? "Equal" : "Not equal");
// "Not equal"

puede que tengas que hacer algo como esto:

function objectEquals(obj1, obj2) {
    for (var i in obj1) {
        if (obj1.hasOwnProperty(i)) {
            if (!obj2.hasOwnProperty(i)) return false;
            if (obj1[i] != obj2[i]) return false;
        }
    }
    for (var i in obj2) {
        if (obj2.hasOwnProperty(i)) {
            if (!obj1.hasOwnProperty(i)) return false;
            if (obj1[i] != obj2[i]) return false;
        }
    }
    return true;
}

Obviamente, esa función podría funcionar con bastante optimización y la capacidad de realizar una verificación profunda (para manejar objetos anidados: var a = { foo : { fu : "bar" } } ), pero se entiende la idea.

Como FOR señaló, puede que tenga que adaptar esto para sus propios fines, por ejemplo: diferentes clases pueden tener diferentes definiciones de "igual". Si solo está trabajando con objetos simples, lo anterior puede ser suficiente, de lo contrario, una MyClass.equals()función personalizada puede ser el camino a seguir.

nickf
fuente
Es un método largo pero prueba completamente los objetos sin hacer ninguna suposición sobre el orden de las propiedades en cada objeto.
briancollins081
22

Si tiene a mano una función de copia profunda, puede utilizar el siguiente truco para seguir utilizando JSON.stringifyel orden de propiedades:

function equals(obj1, obj2) {
    function _equals(obj1, obj2) {
        return JSON.stringify(obj1)
            === JSON.stringify($.extend(true, {}, obj1, obj2));
    }
    return _equals(obj1, obj2) && _equals(obj2, obj1);
}

Demostración: http://jsfiddle.net/CU3vb/3/

Razón fundamental:

Como las propiedades de obj1se copian en el clon una por una, se preservará su orden en el clon. Y cuando las propiedades de obj2se copian en el clon, dado que las propiedades ya existentes obj1simplemente se sobrescribirán, se preservarán sus órdenes en el clon.

Ates Goral
fuente
11
No creo que la preservación del pedido esté garantizada en los navegadores / motores.
Jo Liss
@JoLiss Citas necesarias;) Recuerdo haber probado esto en varios navegadores, obteniendo resultados consistentes. Pero, por supuesto, nadie puede garantizar que el comportamiento sea el mismo en futuros navegadores / motores. Este es un truco (como ya se mencionó en la respuesta) en el mejor de los casos, y no quise decir que fuera una forma segura de comparar objetos.
Ates Goral
1
Claro, aquí hay algunos indicadores: la especificación ECMAScript dice que el objeto está "desordenado" ; y esta respuesta para el comportamiento divergente real en los navegadores actuales.
Jo Liss
2
@JoLiss ¡Gracias por eso! Pero tenga en cuenta que nunca reclamaba la preservación del orden entre el código y el objeto compilado. Estaba reclamando la preservación del orden de las propiedades cuyos valores se reemplazan en el lugar. Esa fue la clave con mi solución: usar un mixin para sobrescribir los valores de propiedad. Suponiendo que las implementaciones generalmente optan por usar algún tipo de hashmap, reemplazar solo los valores debería preservar el orden de las claves. De hecho, es exactamente esto lo que probé en diferentes navegadores.
Ates Goral
1
@AtesGoral: ¿es posible hacer esta restricción un poco más explícita (negrita, ...). La mayoría de las personas simplemente copian y pegan sin leer el texto que lo rodea ...
Willem Van Onsem
21

En Node.js, puede usar su nativo require("assert").deepStrictEqual. Más información: http://nodejs.org/api/assert.html

Por ejemplo:

var assert = require("assert");
assert.deepStrictEqual({a:1, b:2}, {a:1, b:3}); // will throw AssertionError

Otro ejemplo que devuelve true/ en falselugar de devolver errores:

var assert = require("assert");

function deepEqual(a, b) {
    try {
      assert.deepEqual(a, b);
    } catch (error) {
      if (error.name === "AssertionError") {
        return false;
      }
      throw error;
    }
    return true;
};
Rafael Xavier
fuente
Chaitiene esta característica también En su caso, utilizará:var foo = { a: 1 }; var bar = { a: 1 }; expect(foo).to.deep.equal(bar); // true;
Folusho Oladipo
Algunas versiones de Node.js están configuradas error.nameen "AssertionError [ERR_ASSERTION]". En este caso, reemplazaría la declaración if con if (error.code === 'ERR_ASSERTION') {.
Knute Knudsen
No tenía idea de cuál deepStrictEqualera el camino a seguir. Había estado destrozándome el cerebro tratando de entender por qué strictEqualno funcionaba. Fantástico.
NetOperator Wibby
19

Soluciones más simples y lógicas para comparar todo, como objetos, matrices, cadenas, int ...

JSON.stringify({a: val1}) === JSON.stringify({a: val2})

Nota:

  • necesita reemplazar val1y val2con su objeto
  • para el objeto, debe ordenar (por clave) recursivamente para ambos objetos laterales
Pratik Bhalodiya
fuente
66
Supongo que esto no funcionará en muchos casos porque el orden de las teclas en los objetos no importa, a menos JSON.stringifyque un reordenamiento alfabético? (Que no puedo encontrar documentado .)
Bram Vanroy
sí, tienes razón ... para el objeto, tienes que ordenar recursivamente para ambos objetos laterales
Pratik Bhalodiya
2
Esto no funciona para objetos con referencias circulares
Nate-Bit Int
13

Utilizo esta comparablefunción para producir copias de mis objetos que son comparables a JSON:

var comparable = o => (typeof o != 'object' || !o)? o :
  Object.keys(o).sort().reduce((c, key) => (c[key] = comparable(o[key]), c), {});

// Demo:

var a = { a: 1, c: 4, b: [2, 3], d: { e: '5', f: null } };
var b = { b: [2, 3], c: 4, d: { f: null, e: '5' }, a: 1 };

console.log(JSON.stringify(comparable(a)));
console.log(JSON.stringify(comparable(b)));
console.log(JSON.stringify(comparable(a)) == JSON.stringify(comparable(b)));
<div id="div"></div>

Es útil en las pruebas (la mayoría de los marcos de prueba tienen una isfunción). P.ej

is(JSON.stringify(comparable(x)), JSON.stringify(comparable(y)), 'x must match y');

Si se detecta una diferencia, las cadenas se registran, haciendo que las diferencias sean visibles:

x must match y
got      {"a":1,"b":{"0":2,"1":3},"c":7,"d":{"e":"5","f":null}},
expected {"a":1,"b":{"0":2,"1":3},"c":4,"d":{"e":"5","f":null}}.
foque
fuente
1
buena idea (en mi caso, los objetos a comparar son solo pares clave / valor, sin cosas especiales)
mech
10

Heres es una solución en ES6 / ES2015 utilizando un enfoque de estilo funcional:

const typeOf = x => 
  ({}).toString
      .call(x)
      .match(/\[object (\w+)\]/)[1]

function areSimilar(a, b) {
  const everyKey = f => Object.keys(a).every(f)

  switch(typeOf(a)) {
    case 'Array':
      return a.length === b.length &&
        everyKey(k => areSimilar(a.sort()[k], b.sort()[k]));
    case 'Object':
      return Object.keys(a).length === Object.keys(b).length &&
        everyKey(k => areSimilar(a[k], b[k]));
    default:
      return a === b;
  }
}

demo disponible aquí

Alan R. Soares
fuente
No funciona si el orden de las claves de objeto ha cambiado.
Isaac Pak
9

No sé si alguien ha publicado algo similar a esto, pero aquí hay una función que hice para verificar la igualdad de objetos.

function objectsAreEqual(a, b) {
  for (var prop in a) {
    if (a.hasOwnProperty(prop)) {
      if (b.hasOwnProperty(prop)) {
        if (typeof a[prop] === 'object') {
          if (!objectsAreEqual(a[prop], b[prop])) return false;
        } else {
          if (a[prop] !== b[prop]) return false;
        }
      } else {
        return false;
      }
    }
  }
  return true;
}

Además, es recursivo, por lo que también puede verificar la igualdad profunda, si así lo llamas.

Zac
fuente
pequeña corrección: antes de pasar por cada accesorio en ayb, agregue esta comprobación si (Object.getOwnPropertyNames (a) .length! == Object.getOwnPropertyNames (b) .length) devuelve falso
Hith
1
Es obvio que el corrector de igualdad adecuado debe ser recursivo. Creo que una de esas respuestas recursivas debería ser la respuesta correcta. La respuesta aceptada no da código y no ayuda
canbax
9

Para aquellos de ustedes que usan NodeJS, existe un método conveniente llamado isDeepStrictEqualen la biblioteca Util nativa que puede lograr esto.

const util = require('util');

const obj1 = {
  foo: "bar",
  baz: [1, 2]
};

const obj2 = {
  foo: "bar",
  baz: [1, 2]
};


obj1 == obj2 // false
util.isDeepStrictEqual(obj1, obj2) // true

https://nodejs.org/api/util.html#util_util_isdeepstrictequal_val1_val2

Vaelin
fuente
Se supone que su rendimiento es bueno. Sin preocupaciones. Incluso yo uso esto en escenarios complejos también. Como cuando usamos esto, no tenemos que preocuparnos si la propiedad del objeto tiene Object o array. Json.Stringify lo convierte en cadena de todos modos y la comparación de cadenas en javascript no es un gran problema
TrickOrTreat
6

ES6: El código mínimo que podría hacer, es este. Realiza una comparación profunda recursivamente mediante la cadena de todos los objetos, la única limitación es que no se comparan métodos ni símbolos.

const compareObjects = (a, b) => { 
  let s = (o) => Object.entries(o).sort().map(i => { 
     if(i[1] instanceof Object) i[1] = s(i[1]);
     return i 
  }) 
  return JSON.stringify(s(a)) === JSON.stringify(s(b))
}

console.log(compareObjects({b:4,a:{b:1}}, {a:{b:1},b:4}));

Adriano Spadoni
fuente
Esta es una respuesta totalmente funcional, gracias @Adriano Spadoni. ¿Sabes cómo puedo obtener la clave / atributo que se modificó? Gracias,
digitaI
1
hola @digital, si necesitas qué teclas son diferentes, esta no es la función ideal. Verifique la otra respuesta y use una con un ciclo a través de los objetos.
Adriano Spadoni
5

puede usar _.isEqual(obj1, obj2)desde la biblioteca underscore.js.

Aquí hay un ejemplo:

var stooge = {name: 'moe', luckyNumbers: [13, 27, 34]};
var clone  = {name: 'moe', luckyNumbers: [13, 27, 34]};
stooge == clone;
=> false
_.isEqual(stooge, clone);
=> true

Consulte la documentación oficial desde aquí: http://underscorejs.org/#isEqual

Bentaiba Miled Basma
fuente
5

Suponiendo que el orden de las propiedades en el objeto no cambia.

JSON.stringify () funciona para ambos tipos de objetos profundos y no profundos, no muy seguro de los aspectos de rendimiento:

var object1 = {
  key: "value"
};

var object2 = {
  key: "value"
};

var object3 = {
  key: "no value"
};

console.log('object1 and object2 are equal: ', JSON.stringify(object1) === JSON.stringify(object2));

console.log('object2 and object3 are equal: ', JSON.stringify(object2) === JSON.stringify(object3));

Mohammed Zameer
fuente
1
Esto no hace lo que OP quiere, ya que solo coincidirá si ambos objetos tienen las mismas claves, lo que afirman que no tendrán. También requeriría que las claves estén en el mismo orden, lo que tampoco es realmente razonable.
SpeedOfRound
1
¿Cuáles son las propiedades están en diferente orden? No, no es un buen método
Vishal Sakaria
4

Una solución simple a este problema que muchas personas no se dan cuenta es ordenar las cadenas JSON (por carácter). Esto también suele ser más rápido que las otras soluciones mencionadas aquí:

function areEqual(obj1, obj2) {
    var a = JSON.stringify(obj1), b = JSON.stringify(obj2);
    if (!a) a = '';
    if (!b) b = '';
    return (a.split('').sort().join('') == b.split('').sort().join(''));
}

Otra cosa útil sobre este método es que puede filtrar las comparaciones pasando una función "reemplazadora" a las funciones JSON.stringify ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON / stringify # Example_of_using_replacer_parameter ). Lo siguiente solo comparará todas las claves de objetos que se denominan "derp":

function areEqual(obj1, obj2, filter) {
    var a = JSON.stringify(obj1, filter), b = JSON.stringify(obj2, filter);
    if (!a) a = '';
    if (!b) b = '';
    return (a.split('').sort().join('') == b.split('').sort().join(''));
}
var equal = areEqual(obj1, obj2, function(key, value) {
    return (key === 'derp') ? value : undefined;
});
th317erd
fuente
1
Ah, también lo olvidé, pero la función puede acelerarse probando primero la igualación y el rescate anticipado de objetos si son el mismo objeto: if (obj1 === obj2) devuelve verdadero;
th317erd
11
areEqual({a: 'b'}, {b: 'a'})consigue trueentonces?
okm
Sí, me di cuenta después de publicar que esta "solución" tiene problemas. Necesita un poco más de trabajo en el algoritmo de clasificación para funcionar realmente correctamente.
th317erd
4

Solo quería contribuir con mi versión de comparación de objetos utilizando algunas características de es6. No tiene en cuenta un pedido. Después de convertir todos los if / else's a ternary, he venido con lo siguiente:

function areEqual(obj1, obj2) {

    return Object.keys(obj1).every(key => {

            return obj2.hasOwnProperty(key) ?
                typeof obj1[key] === 'object' ?
                    areEqual(obj1[key], obj2[key]) :
                obj1[key] === obj2[key] :
                false;

        }
    )
}
Egor Litvinchuk
fuente
3

Al necesitar una función de comparación de objetos más genérica que la publicada, preparé lo siguiente. Crítica apreciada ...

Object.prototype.equals = function(iObj) {
  if (this.constructor !== iObj.constructor)
    return false;
  var aMemberCount = 0;
  for (var a in this) {
    if (!this.hasOwnProperty(a))
      continue;
    if (typeof this[a] === 'object' && typeof iObj[a] === 'object' ? !this[a].equals(iObj[a]) : this[a] !== iObj[a])
      return false;
    ++aMemberCount;
  }
  for (var a in iObj)
    if (iObj.hasOwnProperty(a))
      --aMemberCount;
  return aMemberCount ? false : true;
}
Liam
fuente
2
Terminé usando una variación de esto. ¡Gracias por la idea de contar miembros!
Nates
2
Tenga mucho cuidado con la modificación Object.prototype: en la gran mayoría de los casos no se recomienda (las adiciones aparecen en todos para ... en bucles, por ejemplo). Tal vez considerar Object.equals = function(aObj, bObj) {...}?
Roy Tinker
3

Si está comparando objetos JSON, puede usar https://github.com/mirek/node-rus-diff

npm install rus-diff

Uso:

a = {foo:{bar:1}}
b = {foo:{bar:1}}
c = {foo:{bar:2}}

var rusDiff = require('rus-diff').rusDiff

console.log(rusDiff(a, b)) // -> false, meaning a and b are equal
console.log(rusDiff(a, c)) // -> { '$set': { 'foo.bar': 2 } }

Si dos objetos son diferentes, {$rename:{...}, $unset:{...}, $set:{...}}se devuelve un objeto similar compatible con MongoDB .

Mirek Rusin
fuente
3

Me enfrenté al mismo problema y decidí escribir mi propia solución. Pero debido a que también quiero comparar matrices con objetos y viceversa, diseñé una solución genérica. Decidí agregar las funciones al prototipo, pero uno puede reescribirlas fácilmente en funciones independientes. Aquí está el código:

Array.prototype.equals = Object.prototype.equals = function(b) {
    var ar = JSON.parse(JSON.stringify(b));
    var err = false;
    for(var key in this) {
        if(this.hasOwnProperty(key)) {
            var found = ar.find(this[key]);
            if(found > -1) {
                if(Object.prototype.toString.call(ar) === "[object Object]") {
                    delete ar[Object.keys(ar)[found]];
                }
                else {
                    ar.splice(found, 1);
                }
            }
            else {
                err = true;
                break;
            }
        }
    };
    if(Object.keys(ar).length > 0 || err) {
        return false;
    }
    return true;
}

Array.prototype.find = Object.prototype.find = function(v) {
    var f = -1;
    for(var i in this) {
        if(this.hasOwnProperty(i)) {
            if(Object.prototype.toString.call(this[i]) === "[object Array]" || Object.prototype.toString.call(this[i]) === "[object Object]") {
                if(this[i].equals(v)) {
                    f = (typeof(i) == "number") ? i : Object.keys(this).indexOf(i);
                }
            }
            else if(this[i] === v) {
                f = (typeof(i) == "number") ? i : Object.keys(this).indexOf(i);
            }
        }
    }
    return f;
}

Este algoritmo se divide en dos partes; La función igual en sí misma y una función para encontrar el índice numérico de una propiedad en una matriz / objeto. La función de búsqueda solo es necesaria porque indexof solo encuentra números y cadenas y no objetos.

Uno puede llamarlo así:

({a: 1, b: "h"}).equals({a: 1, b: "h"});

La función devuelve verdadero o falso, en este caso verdadero. El algoritmo als permite la comparación entre objetos muy complejos:

({a: 1, b: "hello", c: ["w", "o", "r", "l", "d", {answer1: "should be", answer2: true}]}).equals({b: "hello", a: 1, c: ["w", "d", "o", "r", {answer1: "should be", answer2: true}, "l"]})

El ejemplo superior devolverá verdadero, incluso aunque las propiedades tengan un orden diferente. Un pequeño detalle a tener en cuenta: este código también busca el mismo tipo de dos variables, por lo que "3" no es lo mismo que 3.

Sir_baaron
fuente
3

Veo respuestas de código de espagueti. Sin usar ninguna biblioteca de terceros, esto es muy fácil.

En primer lugar, clasifique los dos objetos por sus nombres clave.

let objectOne = { hey, you }
let objectTwo = { you, hey }

// If you really wanted you could make this recursive for deep sort.
const sortObjectByKeyname = (objectToSort) => {
    return Object.keys(objectToSort).sort().reduce((r, k) => (r[k] = objectToSort[k], r), {});
}

let objectOne = sortObjectByKeyname(objectOne)
let objectTwo = sortObjectByKeyname(objectTwo)

Luego, simplemente use una cadena para compararlos.

JSON.stringify(objectOne) === JSON.stringify(objectTwo)
Oliver Dixon
fuente
Esto tampoco funciona para la copia profunda, solo tiene una profundidad de iteración.
andras
Creo que @andras significa que necesita ordenar recursivamente las claves de los objetos anidados.
Davi Lima
2

Aconsejaría contra el hash o la serialización (como sugiere la solución JSON). Si necesita probar si dos objetos son iguales, entonces necesita definir qué significa igual. Puede ser que todos los miembros de datos en ambos objetos coincidan, o puede ser que las ubicaciones de la memoria coincidan (lo que significa que ambas variables hacen referencia al mismo objeto en la memoria), o que solo un miembro de datos en cada objeto debe coincidir.

Recientemente desarrollé un objeto cuyo constructor crea una nueva identificación (comenzando desde 1 e incrementando en 1) cada vez que se crea una instancia. Este objeto tiene una función isEqual que compara ese valor de identificación con el valor de identificación de otro objeto y devuelve verdadero si coinciden.

En ese caso, definí "igual" como el significado de los valores de id. Dado que cada instancia tiene una identificación única, esto podría usarse para forzar la idea de que los objetos coincidentes también ocupan la misma ubicación de memoria. Aunque eso no es necesario.

Bernard Igiri
fuente
2

Es útil considerar dos objetos iguales si tienen los mismos valores para todas las propiedades y recursivamente para todos los objetos y matrices anidados. También considero iguales los siguientes dos objetos:

var a = {p1: 1};
var b = {p1: 1, p2: undefined};

Del mismo modo, las matrices pueden tener elementos "faltantes" y elementos indefinidos. También los trataría igual:

var c = [1, 2];
var d = [1, 2, undefined];

Una función que implementa esta definición de igualdad:

function isEqual(a, b) {
    if (a === b) {
        return true;
    }

    if (generalType(a) != generalType(b)) {
        return false;
    }

    if (a == b) {
        return true;
    }

    if (typeof a != 'object') {
        return false;
    }

    // null != {}
    if (a instanceof Object != b instanceof Object) {
        return false;
    }

    if (a instanceof Date || b instanceof Date) {
        if (a instanceof Date != b instanceof Date ||
            a.getTime() != b.getTime()) {
            return false;
        }
    }

    var allKeys = [].concat(keys(a), keys(b));
    uniqueArray(allKeys);

    for (var i = 0; i < allKeys.length; i++) {
        var prop = allKeys[i];
        if (!isEqual(a[prop], b[prop])) {
            return false;
        }
    }
    return true;
}

Código fuente (incluidas las funciones auxiliares, generalType y uniqueArray): Prueba de unidad y Test Runner aquí .

mckoss
fuente
2

Estoy haciendo los siguientes supuestos con esta función:

  1. Usted controla los objetos que está comparando y solo tiene valores primitivos (es decir, objetos no anidados, funciones, etc.).
  2. Su navegador tiene soporte para Object.keys .

Esto debe tratarse como una demostración de una estrategia simple.

/**
 * Checks the equality of two objects that contain primitive values. (ie. no nested objects, functions, etc.)
 * @param {Object} object1
 * @param {Object} object2
 * @param {Boolean} [order_matters] Affects the return value of unordered objects. (ex. {a:1, b:2} and {b:2, a:1}).
 * @returns {Boolean}
 */
function isEqual( object1, object2, order_matters ) {
    var keys1 = Object.keys(object1),
        keys2 = Object.keys(object2),
        i, key;

    // Test 1: Same number of elements
    if( keys1.length != keys2.length ) {
        return false;
    }

    // If order doesn't matter isEqual({a:2, b:1}, {b:1, a:2}) should return true.
    // keys1 = Object.keys({a:2, b:1}) = ["a","b"];
    // keys2 = Object.keys({b:1, a:2}) = ["b","a"];
    // This is why we are sorting keys1 and keys2.
    if( !order_matters ) {
        keys1.sort();
        keys2.sort();
    }

    // Test 2: Same keys
    for( i = 0; i < keys1.length; i++ ) {
        if( keys1[i] != keys2[i] ) {
            return false;
        }
    }

    // Test 3: Values
    for( i = 0; i < keys1.length; i++ ) {
        key = keys1[i];
        if( object1[key] != object2[key] ) {
            return false;
        }
    }

    return true;
}
Aldo Fregoso
fuente
2

Esta es una adición para todo lo anterior, no un reemplazo. Si necesita hacer una comparación rápida de objetos poco profundos sin necesidad de verificar casos recursivos adicionales. Aquí hay una foto.

Esto se compara para: 1) Igualdad de número de propiedades propias, 2) Igualdad de nombres clave, 3) si bCompareValues ​​== verdadero, Igualdad de los valores de propiedad correspondientes y sus tipos (triple igualdad)

var shallowCompareObjects = function(o1, o2, bCompareValues) {
    var s, 
        n1 = 0,
        n2 = 0,
        b  = true;

    for (s in o1) { n1 ++; }
    for (s in o2) { 
        if (!o1.hasOwnProperty(s)) {
            b = false;
            break;
        }
        if (bCompareValues && o1[s] !== o2[s]) {
            b = false;
            break;
        }
        n2 ++;
    }
    return b && n1 == n2;
}
Lex
fuente
2

Para comparar claves para instancias de objeto de pares clave / valor simples, utilizo:

function compareKeys(r1, r2) {
    var nloops = 0, score = 0;
    for(k1 in r1) {
        for(k2 in r2) {
            nloops++;
            if(k1 == k2)
                score++; 
        }
    }
    return nloops == (score * score);
};

Una vez que se comparan las claves, un simple for..inbucle adicional es suficiente.

La complejidad es O (N * N) con N es el número de claves.

Espero / adivino que los objetos que defino no tendrán más de 1000 propiedades ...

Hefeust CORTES
fuente
2

Sé que esto es un poco viejo, pero me gustaría agregar una solución que se me ocurrió para este problema. Tenía un objeto y quería saber cuándo cambiaron sus datos. "algo similar a Object.observe" y lo que hice fue:

function checkObjects(obj,obj2){
   var values = [];
   var keys = [];
   keys = Object.keys(obj);
   keys.forEach(function(key){
      values.push(key);
   });
   var values2 = [];
   var keys2 = [];
   keys2 = Object.keys(obj2);
   keys2.forEach(function(key){
      values2.push(key);
   });
   return (values == values2 && keys == keys2)
}

Esto aquí puede duplicarse y crear otro conjunto de matrices para comparar los valores y las claves. Es muy simple porque ahora son matrices y devolverán falso si los objetos tienen tamaños diferentes.

inoabrian
fuente
1
Esto siempre regresará false, porque las matrices no se comparan por valor, por ejemplo [1,2] != [1,2].
foque