Comparaison d'objets en JavaScript [dupliquer]

cette question a déjà une réponse ici:

Quelle est la meilleure façon de comparer des objets en JavaScript?

exemple:

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

je sais que deux objets sont égaux s'ils se réfèrent au même objet , mais est-il un moyen de vérifier s'ils ont les mêmes attributs, les valeurs?

La manière suivante fonctionne pour moi, mais est-ce la seule possibilité?

var eq = Object.toJSON(user1) == Object.toJSON(user2);
alert(eq); // gives true
808
demandé sur Matt 2009-07-01 16:18:29

10 réponses

malheureusement, il n'y a pas de solution parfaite, sauf si vous utilisez _proto_ de façon récursive et accédez à toutes les propriétés non-dénombrables, mais cela ne fonctionne qu'avec Firefox.

Donc, le mieux que je puisse faire est de deviner les scénarios d'utilisation.


1) rapide et limitée.

Fonctionne lorsque vous avez simple JSON style des objets sans les méthodes et les nœuds DOM l'intérieur:

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

L'ordre des propriétés est IMPORTANT, donc cette méthode retournera false pour les objets suivants:

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

2) lent et plus générique.

compare des objets sans creuser dans des prototypes, puis compare propriétés projections récursivement, et compare également les constructeurs.

cet algorithme est presque correct:

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

Problèmes connus (bien, ils ont très faible priorité, probablement vous ne les remarquerez jamais):

  • objets ayant une structure prototype différente mais une même projection
  • les fonctions peuvent avoir un texte identique mais se référer à des fermetures différentes

Tests: les tests sont de Comment déterminer l'égalité pour deux objets JavaScript? .

956
répondu crazyx 2017-05-23 12:26:38

voici ma solution commentée dans ES3 (détails sanglants après le code):

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

dans le développement de cette solution, j'ai pris un regard particulier sur les cas de coin, l'efficacité, tout en essayant de produire une solution simple qui fonctionne, espérons-le avec un peu d'élégance. JavaScript permet à la fois null et Non défini les propriétés et les objets ont prototypes chaînes qui peuvent conduire à des comportements très différents si elle n'est pas cochée.

J'ai d'abord choisi d'étendre objet au lieu de objet.prototype , principalement parce que null ne pouvait pas être un des objets de la comparaison et que je crois que null devrait être un objet valide pour comparer avec un autre. Il y a aussi d'autres préoccupations légitimes soulevées par d'autres concernant l'extension de L'objet .prototype concernant les effets secondaires possibles sur le code de l'autre.

un soin particulier doit être apporté à la possibilité que JavaScript permette que les propriétés de l'objet puissent être définies à Non défini , c'est-à-dire qu'il existe des propriétés dont les valeurs sont définies à Non défini . La solution ci-dessus vérifie que les deux objets ont les mêmes propriétés définies à Non défini pour déclarer l'égalité. Ceci ne peut être accompli qu'en vérifiant l'existence de propriétés en utilisant objet.hasOwnProperty (property_name ) . Notez également que JSON.stringify () supprime les propriétés qui sont définis à undefined , et que par conséquent les comparaisons utilisant cette forme ignoreront les propriétés définies à la valeur undefined .

Les fonctions

ne devraient être considérées comme égales que si elles partagent la même référence, et pas seulement le même code, car cela ne tiendrait pas compte de ces fonctions prototype. Donc comparer la chaîne de code ne fonctionne pas pour garantir qu'ils ont la même prototype de l'objet.

les deux objets doivent avoir la même chaîne prototype , pas seulement les mêmes propriétés. Cela ne peut être testé cross-browser en comparant le constructeur des deux objets pour l'égalité stricte. ECMAScript 5 permettrait de tester leur prototype réel en utilisant objet.getPrototypeOf () . Certains navigateurs web aussi offrez une propriété __proto__ qui fait la même chose. Une amélioration possible du code ci-dessus permettrait d'utiliser l'une de ces méthodes chaque fois qu'elle est disponible.

L'utilisation de la stricte comparaisons est primordiale ici parce que 2 ne doit pas être considérée comme égale à "2.0000" , ni false doit être considéré égal à null , Non défini , ou 0 .

Efficacité considérations m'amènent à comparer pour l'égalité des propriétés dès que possible. Alors, seulement si cela a échoué, cherchez le typeof ces propriétés. L'augmentation de vitesse pourrait être significative sur les grands objets avec beaucoup de scalar propriété.

plus besoin de deux boucles, la première pour vérifier les propriétés de l'objet de gauche, la seconde pour vérifier les propriétés de la droite et vérifier seulement l'existence (pas la valeur), pour saisir ces propriétés qui sont définies avec la valeur non définie .

dans l'ensemble ce code traite la plupart des cas de coin en seulement 16 lignes de code (Sans commentaires).

mise à Jour (8/13/2015) . J'ai mis en œuvre une meilleure version, comme la fonction value_equals() qui est plus rapide, traite correctement les cas de coin tels que NaN et 0 différent de -0, application facultative de l'ordre des propriétés des objets et des tests pour les références cycliques, soutenue par Plus de 100 tests automatisés dans le cadre de la Toubkal suite de tests de projet.

154
répondu Jean Vincent 2017-10-12 14:01:50
  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;
};

simple façon de comparer des objets à un seul niveau.

22
répondu pincopallo 2011-05-02 15:27:20

certainement pas le seul moyen - vous pourriez prototype une méthode (contre L'objet ici, mais je ne suggérerais certainement pas d'utiliser L'objet pour le code en direct) pour répliquer C#/Java style comparaison méthodes.

éditer, puisqu'un exemple général semble s'attendre:

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

Note que les tests méthodes toString() est absolument " pas assez bon , mais une méthode qui serait acceptable est très dur à cause du problème de les espaces ayant un sens ou non, sans parler des méthodes synonymes et des méthodes produisant le même résultat avec des implémentations différentes. Et les problèmes de prototypage à l'encontre de l'Objet en général.

20
répondu annakata 2009-07-01 16:53:35

l'algorithme suivant traitera des structures de données auto-référentielles, des nombres, des chaînes, des dates, et bien sûr des objets javascript imbriqués:

les Objets sont considérés comme étant équivalents lorsque

  • ils sont exactement égaux par === (la chaîne et le numéro sont déballés en premier pour s'assurer que 42 est équivalent à Number(42) )
  • ou ils sont deux dates et ont le même valueOf()
  • ou ils sont tous les deux du même type et non nul et...
    • ils ne sont pas des objets et sont égaux par == (captures numéros/cordes les booléens)
    • ou, en ignorant les propriétés avec undefined valeur qu'ils ont les mêmes propriétés qui sont toutes considérées de manière récursive équivalent.

fonctions ne sont pas considérées comme identiques par la fonction texte. Cet essai est insuffisant parce que les fonctions peuvent avoir des fermetures différentes. Les fonctions ne sont considérées comme égales que si === le dit (mais vous pouvez facilement étendre cette relation équivalente si vous choisissez de le faire).

boucles infinies , potentiellement causées par des structures de données circulaires, sont évitées. Quand areEquivalent tente de réfuter l'égalité et revient dans les propriétés d'un objet pour le faire, il garde la trace des objets pour laquelle cette sous-comparaison est nécessaire. Si l'égalité peut être réfutée, alors certains chemins de propriétés accessibles diffèrent entre les objets, et alors il doit y avoir un chemin accessible le plus court, et ce chemin accessible le plus court ne peut pas contenir des cycles présents dans les deux chemins; i.e. il est correct de supposer l'égalité lors de la comparaison récursive des objets. L'hypothèse est stockée dans une propriété areEquivalent_Eq_91_2_34 , qui est supprimée après utilisation, mais si le graphique objet contient déjà une telle propriété, behavior est indéterminé. L'utilisation d'une telle propriété de marqueur est nécessaire parce que javascript ne supporte pas les dictionnaires utilisant des objets arbitraires comme clés.

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
répondu Eamon Nerbonne 2011-06-19 14:29:39

j'ai écrit ce morceau de code pour la comparaison d'objet, et il semble fonctionner. vérifier les assertions:


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
répondu mhoms 2010-10-03 19:56:50

j'ai modifié un peu le code ci-dessus. pour moi 0 != = faux et null != = Non défini . Si vous n'avez pas besoin d'un tel contrôle strict supprimer un " = "sign in" ce[p] !== x[p] " à l'intérieur du code.

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

alors je l'ai testé avec les objets suivants:

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 prévu vrai; a renvoyé true

A = = C prévu false; retourné false

c==d attendre false; retourné false

a==E false attendue; returned false

f= = g attendu true; returned true

h= = g faux attendu; faux retourné

i==j attend vrai; a renvoyé true

d= = k faux attendu; faux retourné

k= = L false attendu; false retourné

5
répondu Jevgeni Kiski 2010-04-29 11:19:16

si vous souhaitez vérifier les méthodes explicitement, vous pouvez utiliser la méthode.toSource () ou méthode.toString() des méthodes.

4
répondu Nicolas R 2009-07-01 15:44:47

voici ma version, à peu près beaucoup de choses de ce thread est intégré (mêmes comptes pour les cas de test):

Object.defineProperty(Object.prototype, "equals", {
    enumerable: false,
    value: function (obj) {
        var p;
        if (this === obj) {
            return true;
        }

        // some checks for native types first

        // function and sring
        if (typeof(this) === "function" || typeof(this) === "string" || this instanceof String) { 
            return this.toString() === obj.toString();
        }

        // number
        if (this instanceof Number || typeof(this) === "number") {
            if (obj instanceof Number || typeof(obj) === "number") {
                return this.valueOf() === obj.valueOf();
            }
            return false;
        }

        // null.equals(null) and undefined.equals(undefined) do not inherit from the 
        // Object.prototype so we can return false when they are passed as obj
        if (typeof(this) !== typeof(obj) || obj === null || typeof(obj) === "undefined") {
            return false;
        }

        function sort (o) {
            var result = {};

            if (typeof o !== "object") {
                return o;
            }

            Object.keys(o).sort().forEach(function (key) {
                result[key] = sort(o[key]);
            });

            return result;
        }

        if (typeof(this) === "object") {
            if (Array.isArray(this)) { // check on arrays
                return JSON.stringify(this) === JSON.stringify(obj);                
            } else { // anyway objects
                for (p in this) {
                    if (typeof(this[p]) !== typeof(obj[p])) {
                        return false;
                    }
                    if ((this[p] === null) !== (obj[p] === null)) {
                        return false;
                    }
                    switch (typeof(this[p])) {
                    case 'undefined':
                        if (typeof(obj[p]) !== 'undefined') {
                            return false;
                        }
                        break;
                    case 'object':
                        if (this[p] !== null 
                                && obj[p] !== null 
                                && (this[p].constructor.toString() !== obj[p].constructor.toString() 
                                        || !this[p].equals(obj[p]))) {
                            return false;
                        }
                        break;
                    case 'function':
                        if (this[p].toString() !== obj[p].toString()) {
                            return false;
                        }
                        break;
                    default:
                        if (this[p] !== obj[p]) {
                            return false;
                        }
                    }
                };

            }
        }

        // at least check them with JSON
        return JSON.stringify(sort(this)) === JSON.stringify(sort(obj));
    }
});

Voici mon TestCase:

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

    assertTrue("String", "hi".equals("hi"));
    assertTrue("Number", new Number(5).equals(5));
    assertFalse("Number", new Number(5).equals(10));
    assertFalse("Number+String", 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}}));

    assertTrue("Function", (function(x){return x;}).equals(function(x){return x;}));
    assertFalse("Function", (function(x){return x;}).equals(function(y){return y+2;}));

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

    assertTrue(a.equals(b));
    assertFalse(a.equals(c));
    assertFalse(c.equals(d));
    assertFalse(a.equals(e));
    assertTrue(f.equals(g));
    assertFalse(h.equals(g));
    assertTrue(i.equals(j));
    assertFalse(d.equals(k));
    assertFalse(k.equals(l));
4
répondu gossi 2011-04-02 11:39:24

si vous travaillez sans la bibliothèque JSON, peut-être que cela vous aidera:

Object.prototype.equals = function(b) {
    var a = this;
    for(i in a) {
        if(typeof b[i] == 'undefined') {
            return false;
        }
        if(typeof b[i] == 'object') {
            if(!b[i].equals(a[i])) {
                return false;
            }
        }
        if(b[i] != a[i]) {
            return false;
        }
    }
    for(i in b) {
        if(typeof a[i] == 'undefined') {
            return false;
        }
        if(typeof a[i] == 'object') {
            if(!a[i].equals(b[i])) {
                return false;
            }
        }
        if(a[i] != b[i]) {
            return false;
        }
    }
    return true;
}

var a = {foo:'bar', bar: {blub:'bla'}};
var b = {foo:'bar', bar: {blub:'blob'}};
alert(a.equals(b)); // alert's a false
3
répondu Samuel Weber 2014-08-09 16:22:46