¿Cómo puedo determinar la igualdad de dos objetos JavaScript?
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 .
¿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.
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 Equals
se 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, c
no es realmente importante determinar si dos instancias cualesquiera de MyClass son iguales, sólo a
son b
importantes. 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?
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
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);
}
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 } }));