Pregunta Comparación de objetos en JavaScript [duplicado]


Esta pregunta ya tiene una respuesta aquí:

¿Cuál es la mejor forma de comparar objetos en JavaScript?

Ejemplo:

var user1 = {name : "nerd", org: "dev"};
var user2 = {name : "nerd", org: "dev"};
var eq = user1 == user2;
alert(eq); // gives false

Yo sé eso dos objetos son iguales si se refieren exactamente al mismo objeto, pero ¿hay alguna manera de comprobar si tienen los mismos valores de atributos?

La siguiente manera me funciona, ¿pero es la única posibilidad?

var eq = Object.toJSON(user1) == Object.toJSON(user2);
alert(eq); // gives true

785
2017-07-01 12:18


origen


Respuestas:


Desafortunadamente no hay una manera perfecta, a menos que uses _proto_ recursivamente y acceder a todas las propiedades no enumerables, pero esto solo funciona en Firefox.

Entonces lo mejor que puedo hacer es adivinar escenarios de uso.


1) rápido y limitado

Funciona cuando tienes objetos simples estilo JSON sin métodos y nodos DOM dentro:

 JSON.stringify(obj1) === JSON.stringify(obj2) 

El ORDEN de las propiedades ES IMPORTANTE, por lo que este método devolverá falso para los siguientes objetos:

 x = {a: 1, b: 2};
 y = {b: 2, a: 1};

2) Lento y más genérico.

Compara objetos sin excavar en prototipos, luego compara las proyecciones de las propiedades recursivamente y también compara constructores.

Este es un algoritmo casi correcto:

function deepCompare () {
  var i, l, leftChain, rightChain;

  function compare2Objects (x, y) {
    var p;

    // remember that NaN === NaN returns false
    // and isNaN(undefined) returns true
    if (isNaN(x) && isNaN(y) && typeof x === 'number' && typeof y === 'number') {
         return true;
    }

    // Compare primitives and functions.     
    // Check if both arguments link to the same object.
    // Especially useful on the step where we compare prototypes
    if (x === y) {
        return true;
    }

    // Works in case when functions are created in constructor.
    // Comparing dates is a common scenario. Another built-ins?
    // We can even handle functions passed across iframes
    if ((typeof x === 'function' && typeof y === 'function') ||
       (x instanceof Date && y instanceof Date) ||
       (x instanceof RegExp && y instanceof RegExp) ||
       (x instanceof String && y instanceof String) ||
       (x instanceof Number && y instanceof Number)) {
        return x.toString() === y.toString();
    }

    // At last checking prototypes as good as we can
    if (!(x instanceof Object && y instanceof Object)) {
        return false;
    }

    if (x.isPrototypeOf(y) || y.isPrototypeOf(x)) {
        return false;
    }

    if (x.constructor !== y.constructor) {
        return false;
    }

    if (x.prototype !== y.prototype) {
        return false;
    }

    // Check for infinitive linking loops
    if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1) {
         return false;
    }

    // Quick checking of one object being a subset of another.
    // todo: cache the structure of arguments[0] for performance
    for (p in y) {
        if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
            return false;
        }
        else if (typeof y[p] !== typeof x[p]) {
            return false;
        }
    }

    for (p in x) {
        if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
            return false;
        }
        else if (typeof y[p] !== typeof x[p]) {
            return false;
        }

        switch (typeof (x[p])) {
            case 'object':
            case 'function':

                leftChain.push(x);
                rightChain.push(y);

                if (!compare2Objects (x[p], y[p])) {
                    return false;
                }

                leftChain.pop();
                rightChain.pop();
                break;

            default:
                if (x[p] !== y[p]) {
                    return false;
                }
                break;
        }
    }

    return true;
  }

  if (arguments.length < 1) {
    return true; //Die silently? Don't know how to handle such case, please help...
    // throw "Need two or more arguments to compare";
  }

  for (i = 1, l = arguments.length; i < l; i++) {

      leftChain = []; //Todo: this can be cached
      rightChain = [];

      if (!compare2Objects(arguments[0], arguments[i])) {
          return false;
      }
  }

  return true;
}

Problemas conocidos (bueno, tienen muy baja prioridad, probablemente nunca los notarás):

  • objetos con diferente estructura de prototipo pero misma proyección
  • las funciones pueden tener texto idéntico pero se refieren a cierres diferentes

Pruebas: pasa pruebas son de ¿Cómo se determina la igualdad para dos objetos de JavaScript?.


923
2017-07-17 16:08



Aquí está mi solución comentada en ES3 (detalles sangrientos después del código):

Object.equals = function( x, y ) {
  if ( x === y ) return true;
    // if both x and y are null or undefined and exactly the same

  if ( ! ( x instanceof Object ) || ! ( y instanceof Object ) ) return false;
    // if they are not strictly equal, they both need to be Objects

  if ( x.constructor !== y.constructor ) return false;
    // they must have the exact same prototype chain, the closest we can do is
    // test there constructor.

  for ( var p in x ) {
    if ( ! x.hasOwnProperty( p ) ) continue;
      // other properties were tested using x.constructor === y.constructor

    if ( ! y.hasOwnProperty( p ) ) return false;
      // allows to compare x[ p ] and y[ p ] when set to undefined

    if ( x[ p ] === y[ p ] ) continue;
      // if they have the same strict value or identity then they are equal

    if ( typeof( x[ p ] ) !== "object" ) return false;
      // Numbers, Strings, Functions, Booleans must be strictly equal

    if ( ! Object.equals( x[ p ],  y[ p ] ) ) return false;
      // Objects and Arrays must be tested recursively
  }

  for ( p in y ) {
    if ( y.hasOwnProperty( p ) && ! x.hasOwnProperty( p ) ) return false;
      // allows x[ p ] to be set to undefined
  }
  return true;
}

Al desarrollar esta solución, eché un vistazo particular a los casos de esquina, la eficiencia, pero tratando de ofrecer una solución simple que funcione, con suerte con algo de elegancia. JavaScript permite ambos nulo y indefinido propiedades y objetos tienen cadenas de prototipos eso puede llevar a comportamientos muy diferentes si no se controlan.

Primero he elegido extender Objeto en lugar de Object.prototype, mayormente porque nulo no podría ser uno de los objetos de la comparación y creo que nulo debería ser un objeto válido para comparar con otro. También hay otras preocupaciones legítimas observadas por otros con respecto a la extensión de Object.prototype con respecto a los posibles efectos secundarios en el código de otros.

Se debe tener especial cuidado para tratar la posibilidad de que JavaScript permita que las propiedades del objeto se puedan configurar para indefinido, es decir, existen propiedades cuyos valores están configurados para indefinido. La solución anterior verifica que ambos objetos tienen las mismas propiedades establecidas para indefinido para informar la igualdad. Esto solo se puede lograr al verificar la existencia de propiedades usando Object.hasOwnProperty (property_name). También tenga en cuenta que JSON.stringify () elimina las propiedades que están configuradas para indefinido, y que, por lo tanto, las comparaciones que usan este formulario ignorarán las propiedades establecidas en el valor indefinido.

Las funciones deben considerarse iguales solo si comparten la misma referencia, no solo el mismo código, porque esto no tomaría en cuenta estas funciones prototipo. Entonces, comparar la cadena de código no funciona para garantizar que tengan el mismo objeto prototipo.

Los dos objetos deben tener el mismo cadena de prototipos, no solo las mismas propiedades Esto solo se puede probar en varios navegadores comparando el constructor de ambos objetos para la igualdad estricta. ECMAScript 5 permitiría probar su prototipo real usando Object.getPrototypeOf (). Algunos navegadores web también ofrecen una __proto__ propiedad que hace lo mismo. Una posible mejora del código anterior permitiría utilizar uno de estos métodos siempre que esté disponible.

El uso de comparaciones estrictas es primordial aquí porque 2 no debe considerarse igual a "2.0000", ni falso debe ser considerado igual a nulo, indefinido, o 0.

Las consideraciones de eficiencia me llevan a comparar lo más pronto posible la igualdad de propiedades. Entonces, solo si eso falló, busca el tipo de estas propiedades. El aumento de velocidad podría ser significativo en objetos grandes con muchas propiedades escalares.

No se requieren más de dos bucles, el primero para comprobar las propiedades del objeto izquierdo, el segundo para verificar las propiedades de la derecha y verificar solo la existencia (no el valor), para capturar estas propiedades que se definen con el indefinido valor.

En general, este código maneja la mayoría de los casos de esquina en solo 16 líneas de código (sin comentarios).

Actualización (13/13/2015). He implementado una mejor versión, ya que la función value_equals () eso es más rápido, maneja casos de esquinas adecuados como NaN y 0 diferentes de -0, opcionalmente impone el orden de propiedades de los objetos y prueba de referencias cíclicas, respaldados por más de 100 pruebas automatizadas como parte de la Toubkal suite de prueba de proyectos.


154
2017-07-15 22:21



  Utils.compareObjects = function(o1, o2){
    for(var p in o1){
        if(o1.hasOwnProperty(p)){
            if(o1[p] !== o2[p]){
                return false;
            }
        }
    }
    for(var p in o2){
        if(o2.hasOwnProperty(p)){
            if(o1[p] !== o2[p]){
                return false;
            }
        }
    }
    return true;
};

Manera simple de comparar objetos de UN NIVEL.


21
2018-05-02 15:27



Ciertamente, no de la única manera: podría prototipar un método (contra Object aquí, pero ciertamente no sugeriría usar Object for live code) para replicar métodos de comparación de estilo C # / Java.

Editar, ya que parece esperarse un ejemplo general:

Object.prototype.equals = function(x)
{
    for(p in this)
    {
        switch(typeof(this[p]))
        {
            case 'object':
                if (!this[p].equals(x[p])) { return false }; break;
            case 'function':
                if (typeof(x[p])=='undefined' || (p != 'equals' && this[p].toString() != x[p].toString())) { return false; }; break;
            default:
                if (this[p] != x[p]) { return false; }
        }
    }

    for(p in x)
    {
        if(typeof(this[p])=='undefined') {return false;}
    }

    return true;
}

Tenga en cuenta que los métodos de prueba con toString () son absolutamente no lo suficientemente bueno pero un método que sería aceptable es muy difícil debido al problema de que los espacios en blanco tengan o no significado, sin olvidar los métodos y métodos sinónimos que producen el mismo resultado con diferentes implementaciones. Y los problemas de creación de prototipos contra Objeto en general.


19
2017-07-01 12:25



El siguiente algoritmo se ocupará de estructuras de datos autorreferenciales, números, cadenas, fechas y, por supuesto, objetos javascript anidados simples:

Los objetos se consideran equivalentes cuando

  • Son exactamente iguales por === (String y Number se desenvuelven primero para garantizar 42 es equivalente a Number(42))
  • o ambas son fechas y tienen el mismo valueOf()
  • o ambos son del mismo tipo y no nulos y ...
    • no son objetos y son iguales por == (atrapa números / cadenas / booleanos)
    • o ignorando propiedades con undefined valor tienen las mismas propiedades, todas consideradas recursivamente equivalentes.

Funciones no se consideran idénticos por el texto de la función. Esta prueba es insuficiente porque las funciones pueden tener cierres diferentes. Las funciones solo se consideran iguales si === lo dice (pero podría extender fácilmente esa relación equivalente si decide hacerlo).

Bucles infinitos, potencialmente causado por estructuras de datos circulares, se evitan. Cuando areEquivalent intenta desmentir la igualdad y vuelve a recurrir a las propiedades de un objeto para hacerlo; realiza un seguimiento de los objetos para los que se necesita esta subcomparación. Si se puede refutar la igualdad, entonces alguna ruta de propiedad alcanzable difiere entre los objetos, y luego debe haber una ruta accesible más corta, y la ruta accesible más corta no puede contener ciclos presentes en ambas rutas; es decir, está bien suponer igualdad cuando se comparan recursivamente objetos. La suposición se almacena en una propiedad areEquivalent_Eq_91_2_34, que se elimina después de su uso, pero si el gráfico de objetos ya contiene dicha propiedad, el comportamiento no está definido. El uso de dicha propiedad de marcador es necesario porque JavaScript no admite diccionarios que usen objetos arbitrarios como claves.

function unwrapStringOrNumber(obj) {
    return (obj instanceof Number || obj instanceof String 
            ? obj.valueOf() 
            : obj);
}
function areEquivalent(a, b) {
    a = unwrapStringOrNumber(a);
    b = unwrapStringOrNumber(b);
    if (a === b) return true; //e.g. a and b both null
    if (a === null || b === null || typeof (a) !== typeof (b)) return false;
    if (a instanceof Date) 
        return b instanceof Date && a.valueOf() === b.valueOf();
    if (typeof (a) !== "object") 
        return a == b; //for boolean, number, string, xml

    var newA = (a.areEquivalent_Eq_91_2_34 === undefined),
        newB = (b.areEquivalent_Eq_91_2_34 === undefined);
    try {
        if (newA) a.areEquivalent_Eq_91_2_34 = [];
        else if (a.areEquivalent_Eq_91_2_34.some(
            function (other) { return other === b; })) return true;
        if (newB) b.areEquivalent_Eq_91_2_34 = [];
        else if (b.areEquivalent_Eq_91_2_34.some(
            function (other) { return other === a; })) return true;
        a.areEquivalent_Eq_91_2_34.push(b);
        b.areEquivalent_Eq_91_2_34.push(a);

        var tmp = {};
        for (var prop in a) 
            if(prop != "areEquivalent_Eq_91_2_34") 
                tmp[prop] = null;
        for (var prop in b) 
            if (prop != "areEquivalent_Eq_91_2_34") 
                tmp[prop] = null;

        for (var prop in tmp) 
            if (!areEquivalent(a[prop], b[prop]))
                return false;
        return true;
    } finally {
        if (newA) delete a.areEquivalent_Eq_91_2_34;
        if (newB) delete b.areEquivalent_Eq_91_2_34;
    }
}

15
2018-06-19 14:29



Escribí este fragmento de código para comparar objetos, y parece funcionar. verificar las aserciones:


function countProps(obj) {
    var count = 0;
    for (k in obj) {
        if (obj.hasOwnProperty(k)) {
            count++;
        }
    }
    return count;
};

function objectEquals(v1, v2) {

    if (typeof(v1) !== typeof(v2)) {
        return false;
    }

    if (typeof(v1) === "function") {
        return v1.toString() === v2.toString();
    }

    if (v1 instanceof Object && v2 instanceof Object) {
        if (countProps(v1) !== countProps(v2)) {
            return false;
        }
        var r = true;
        for (k in v1) {
            r = objectEquals(v1[k], v2[k]);
            if (!r) {
                return false;
            }
        }
        return true;
    } else {
        return v1 === v2;
    }
}

assert.isTrue(objectEquals(null,null));
assert.isFalse(objectEquals(null,undefined));

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

assert.isTrue(objectEquals(function(x){return x;},function(x){return x;}));
assert.isFalse(objectEquals(function(x){return x;},function(y){return y+2;}));

11
2017-10-03 11:01



He modificado un poco el código anterior. para mi 0! == falso y null! == undefined. Si no necesita un cheque tan estricto, elimine uno "=" registrarse "esto [p]! == x [p]"dentro del código.

Object.prototype.equals = function(x){
    for (var p in this) {
        if(typeof(this[p]) !== typeof(x[p])) return false;
        if((this[p]===null) !== (x[p]===null)) return false;
        switch (typeof(this[p])) {
            case 'undefined':
                if (typeof(x[p]) != 'undefined') return false;
                break;
            case 'object':
                if(this[p]!==null && x[p]!==null && (this[p].constructor.toString() !== x[p].constructor.toString() || !this[p].equals(x[p]))) return false;
                break;
            case 'function':
                if (p != 'equals' && this[p].toString() != x[p].toString()) return false;
                break;
            default:
                if (this[p] !== x[p]) return false;
        }
    }
    return true;
}

Luego lo he probado con los siguientes objetos:

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 f = {a: 'text', b:[1,0], f: function(){ this.f = this.b; }};
var g = {a: 'text', b:[1,0], f: function(){ this.f = this.b; }};
var h = {a: 'text', b:[1,0], f: function(){ this.a = this.b; }};
var i = {
    a: 'text',
    c: {
        b: [1, 0],
        f: function(){
            this.a = this.b;
        }
    }
};
var j = {
    a: 'text',
    c: {
        b: [1, 0],
        f: function(){
            this.a = this.b;
        }
    }
};
var k = {a: 'text', b: null};
var l = {a: 'text', b: undefined};

a == b esperado verdadero; devuelto verdadero

a == c esperado falso; devuelto falso

c == d esperado falso; devuelto falso

a == e esperado falso; devuelto falso

f == g esperado verdadero; devuelto verdadero

h == g esperado falso; devuelto falso

i == j esperado verdad; devuelto verdadero

d == k esperado falso; devuelto falso

k == l esperado falso; devuelto falso


5
2018-04-29 09:17