Comment déterminer l'égalité pour deux objets JavaScript?

670

Un opérateur d'égalité stricte vous dira si deux types d' objet sont égaux. Cependant, existe-t-il un moyen de savoir si deux objets sont égaux, un peu comme la valeur du code de hachage en Java?

Question de dépassement de pile Existe-t-il une sorte de fonction hashCode en JavaScript? est similaire à cette question, mais nécessite une réponse plus académique. Le scénario ci-dessus montre pourquoi il serait nécessaire d'en avoir un, et je me demande s'il existe une solution équivalente .

Communauté
la source
3
Regardez également cette question stackoverflow.com/q/1068834/1671639
Praveen
37
Notez que, même en Java, a.hashCode() == b.hashCode()n'implique pas que ce asoit égal à b. C'est une condition nécessaire, pas suffisante.
Heinzi
Veuillez consulter ce qs: stackoverflow.com/questions/35933616/…
forgottofly
2
Si vous devez comparer des objets dans votre code, vous écrivez probablement mal votre code. La meilleure question pourrait être: "Comment puis-je écrire ce code pour ne pas avoir à comparer des objets?"
th317erd
3
@ th317erd pouvez-vous s'il vous plaît vous expliquer? ...
El Mac

Réponses:

175

La réponse courte

La réponse est simple: non, il n'y a pas de moyen générique pour déterminer qu'un objet est égal à un autre dans le sens que vous entendez. L'exception est lorsque vous pensez strictement à un objet sans type.

La réponse longue

Le concept est celui d'une méthode Equals qui compare deux instances différentes d'un objet pour indiquer si elles sont égales à un niveau de valeur. Cependant, c'est au type spécifique de définir comment une Equalsméthode doit être implémentée. Une comparaison itérative d'attributs qui ont des valeurs primitives peut ne pas être suffisante, il peut bien y avoir des attributs qui ne doivent pas être considérés comme faisant partie de la valeur de l'objet. Par exemple,

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

Dans ce cas ci-dessus, il cn'est pas vraiment important de déterminer si deux instances de MyClass sont égales, uniquement aet bsont importantes. Dans certains cas, cela cpeut varier d'une instance à l'autre et ne pas être significatif lors de la comparaison.

Notez que ce problème s'applique lorsque les membres peuvent eux-mêmes être des instances d'un type et que chacun d'eux devrait tous avoir un moyen de déterminer l'égalité.

Pour compliquer encore les choses, en JavaScript, la distinction entre données et méthode est floue.

Un objet peut référencer une méthode qui doit être appelée en tant que gestionnaire d'événements, et cela ne serait probablement pas considéré comme faisant partie de son «état de valeur». Alors qu'un autre objet peut très bien se voir attribuer une fonction qui effectue un calcul important et rend ainsi cette instance différente des autres simplement parce qu'elle fait référence à une fonction différente.

Qu'en est-il d'un objet dont l'une de ses méthodes de prototype existantes est remplacée par une autre fonction? Pourrait-elle encore être considérée comme égale à une autre instance qu'elle serait par ailleurs identique? Cette question ne peut être répondue que dans chaque cas spécifique pour chaque type.

Comme indiqué précédemment, l'exception serait un objet strictement sans type. Dans ce cas, le seul choix judicieux est une comparaison itérative et récursive de chaque membre. Même alors, il faut se demander quelle est la «valeur» d'une fonction?

AnthonyWJones
la source
176
Si vous utilisez un trait de soulignement, vous pouvez simplement le faire_.isEqual(obj1, obj2);
chovy
12
@Harsh, la réponse n'a donné aucune solution car il n'y en a pas. Même en Java, il n'y a pas de solution miracle pour comparer l'égalité des objets et pour implémenter correctement la .equalsméthode n'est pas anodin, c'est pourquoi il y a un tel sujet dédié dans Java efficace .
lcn
3
@Kumar Harsh, Ce qui rend deux objets égaux est très spécifique à l'application; toutes les propriétés d'un objet ne doivent pas nécessairement être prises en considération, donc forcer brutalement chaque propriété d'un objet n'est pas non plus une solution concrète.
sethro
googlé javascript equality object, obtenu tl; dr réponse, a pris une ligne du commentaire @chovy. merci
Andrea
Si vous utilisez angulaire, vous avezangular.equals
boatcoder
509

Pourquoi réinventer la roue? Essayez Lodash . Il a un certain nombre de fonctions indispensables telles que isEqual () .

_.isEqual(object, other);

Il vérifiera brutalement chaque valeur de clé - tout comme les autres exemples de cette page - en utilisant ECMAScript 5 et des optimisations natives si elles sont disponibles dans le navigateur.

Remarque: Auparavant, cette réponse recommandait Underscore.js , mais lodash a fait un meilleur travail pour corriger les bogues et résoudre les problèmes avec cohérence.

CoolAJ86
la source
27
La fonction isEqual de Underscore est très agréable (mais vous devez tirer dans leur bibliothèque pour l'utiliser - environ 3K gzippé).
mckoss
29
si vous regardez ce que le soulignement vous donne d'autre, vous ne regretterez pas de l'avoir fait
PandaWood
6
Même si vous ne pouvez pas vous permettre d'avoir un trait de soulignement comme dépendance, retirez la fonction isEqual, remplissez les conditions de licence et passez à autre chose. C'est de loin le test d'égalité le plus complet mentionné sur stackoverflow.
Dale Anderson
7
Il y a un fork d'Underscore appelé LoDash et cet auteur est très préoccupé par des problèmes de cohérence comme celui-ci. Testez avec LoDash et voyez ce que vous obtenez.
CoolAJ86
6
@mckoss vous pouvez utiliser le module autonome si vous ne voulez pas la bibliothèque entière npmjs.com/package/lodash.isequal
Rob Fox
161

L'opérateur d'égalité par défaut dans JavaScript pour les objets donne la valeur true lorsqu'ils se réfèrent au même emplacement en mémoire.

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

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

Si vous avez besoin d'un opérateur d'égalité différent, vous devrez ajouter une equals(other)méthode, ou quelque chose comme ça à vos classes et les spécificités de votre domaine problématique détermineront ce que cela signifie exactement.

Voici un exemple de carte à jouer:

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
la source
Si le ou les objets peuvent être convertis en chaîne JSON, cela rend la fonction equals () simple.
scotts
3
@scotts Pas toujours. La conversion d'objets en JSON et la comparaison de chaînes peuvent devenir gourmandes en calcul pour les objets complexes en boucles serrées. Pour les objets simples, cela n'a probablement pas beaucoup d'importance, mais en réalité cela dépend vraiment de votre situation spécifique. Une solution correcte peut être aussi simple que de comparer des ID d'objet ou de vérifier chaque propriété, mais son exactitude est entièrement dictée par le domaine problématique.
Daniel X Moore
Ne devrions-nous pas également comparer le type de données?! return other.rank === this.rank && other.suit === this.suit;
devsathish
1
@devsathish probablement pas. En JavaScript, les types sont assez rapides et lâches, mais si dans votre domaine les types sont importants, vous pouvez également vérifier les types.
Daniel X Moore
7
@scotts Un autre problème avec la conversion en JSON est que l'ordre des propriétés dans la chaîne devient significatif. {x:1, y:2}! =={y:2, x:1}
Stijn de Witt
81

Si vous travaillez dans AngularJS , la angular.equalsfonction déterminera si deux objets sont égaux. Dans Ember.js, utilisez isEqual.

  • angular.equals- Voir les documents ou la source pour en savoir plus sur cette méthode. Il effectue également une comparaison approfondie sur les tableaux.
  • Ember.js isEqual- Voir la documentation ou la source pour en savoir plus sur cette méthode. Il ne fait pas de comparaison approfondie sur les tableaux.

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
la source
66

Ceci est ma version. Il utilise la nouvelle fonctionnalité Object.keys introduite dans ES5 et les idées / tests de + , + et + :

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
la source
objectEquals([1,2,undefined],[1,2])retourstrue
Roy Tinker
objectEquals([1,2,3],{0:1,1:2,2:3})renvoie également true- par exemple, il n'y a pas de vérification de type, uniquement une vérification de clé / valeur.
Roy Tinker
objectEquals(new Date(1234),1234)retournetrue
Roy Tinker
1
if (x.constructor! == y.constructor) {return false; } Cela se briserait lors de la comparaison de deux 'nouvelle chaîne (' a ')' dans différentes fenêtres. Pour l'égalité des valeurs, vous devez vérifier si String.isString sur les deux objets, puis utiliser une vérification d'égalité lâche 'a == b'.
Triynko
1
Il y a une énorme différence entre l'égalité "valeur" et l'égalité "stricte" et elles ne devraient pas être mises en œuvre de la même manière. L'égalité des valeurs ne devrait pas se soucier des types, en dehors de la structure de base, qui est l'une de ces 4: «objet» (c'est-à-dire une collection de paires clé / valeur), «nombre», «chaîne» ou «tableau». C'est ça. Tout ce qui n'est pas un nombre, une chaîne ou un tableau doit être comparé en tant qu'ensemble de paires clé / valeur, quel que soit le constructeur (cross-window-safe). Lorsque vous comparez des objets, assimilez la valeur des nombres littéraux et des instances de Number, mais ne contraignez pas les chaînes aux nombres.
Triynko
50

Si vous utilisez une bibliothèque JSON, vous pouvez coder chaque objet en JSON, puis comparer l'égalité des chaînes résultantes.

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

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

REMARQUE: Bien que cette réponse fonctionne dans de nombreux cas, comme plusieurs personnes l'ont souligné dans les commentaires, elle est problématique pour diverses raisons. Dans presque tous les cas, vous souhaiterez trouver une solution plus robuste.

Joel Anair
la source
91
Intéressant, mais un peu délicat à mon avis. Par exemple, pouvez-vous garantir à 100% que les propriétés de l'objet seront toujours générées dans le même ordre?
Guido
25
C'est une bonne question, et en soulève une autre, de savoir si deux objets ayant les mêmes propriétés dans des ordres différents sont vraiment égaux ou non. Cela dépend de ce que vous entendez par égal, je suppose.
Joel Anair
11
Notez que la plupart des encodeurs et des stringificateurs ignorent les fonctions et convertissent les nombres non finis, comme NaN, en null.
Stephen Belanger
4
Je suis d'accord avec Guido, l'ordre des propriétés est important et ne peut être garanti. @JoelAnair, je pense que deux objets avec les mêmes propriétés dans des ordres différents devraient être considérés comme égaux si la valeur des propriétés est égale.
Juzer Ali
5
Cela pourrait fonctionner avec un autre stringifier JSON, qui trie les clés d'objet de manière cohérente.
Roy Tinker
40

Implémentation fonctionnelle courte 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);
}

Edit : version 2, en utilisant la suggestion de jib et les fonctions flèches 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
la source
5
Vous pouvez remplacer reducepar everypour simplifier.
potence
1
@nonkertompf sûr qu'il pourrait: Object.keys(x).every(key => deepEqual(x[key], y[key])).
foc
2
Cela échoue lorsque vous comparez deux dates
Greg
3
deepEqual ({}, []) retourne vrai
AlexMorley-Finch
3
oui, si vous vous souciez d'un tel cas d'angle, la solution laide est de remplacer : (x === y)par: (x === y && (x != null && y != null || x.constructor === y.constructor))
au
22

Essayez-vous de tester si deux objets sont égaux? c'est à dire: leurs propriétés sont égales?

Si tel est le cas, vous aurez probablement remarqué cette situation:

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

vous devrez peut-être faire quelque chose comme ceci:

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

Évidemment, cette fonction pourrait faire un peu d'optimisation et la possibilité de faire une vérification approfondie (pour gérer les objets imbriqués: var a = { foo : { fu : "bar" } } mais vous avez l'idée.

Comme FOR l'a souligné, vous devrez peut-être l'adapter à vos propres besoins, par exemple: différentes classes peuvent avoir différentes définitions de «égal». Si vous travaillez uniquement avec des objets simples, ce qui précède peut suffire, sinon une MyClass.equals()fonction personnalisée peut être la solution.

nickf
la source
C'est une méthode longue mais elle teste complètement les objets sans faire d'hypothèses sur l'ordre des propriétés de chaque objet.
briancollins081
22

Si vous avez une fonction de copie approfondie à portée de main, vous pouvez utiliser l'astuce suivante pour continuer à utiliser JSON.stringifytout en respectant l'ordre des propriétés:

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

Démo: http://jsfiddle.net/CU3vb/3/

Raisonnement:

Étant donné que les propriétés de obj1sont copiées dans le clone une par une, leur ordre dans le clone sera conservé. Et lorsque les propriétés de obj2sont copiées dans le clone, puisque les propriétés déjà existantes dans obj1seront simplement écrasées, leurs ordres dans le clone seront préservés.

Ates Goral
la source
11
Je ne pense pas que la préservation de l'ordre soit garantie sur tous les navigateurs / moteurs.
Jo Liss
@JoLiss Citations nécessaires;) Je me souviens avoir testé cela dans plusieurs navigateurs, obtenant des résultats cohérents. Mais bien sûr, personne ne peut garantir que le comportement reste le même dans les futurs navigateurs / moteurs. C'est une astuce (comme déjà mentionné dans la réponse) au mieux, et je ne voulais pas que ce soit un moyen infaillible de comparer des objets.
Ates Goral
1
Bien sûr, voici quelques conseils: la spécification ECMAScript indique que l'objet est "non ordonné" ; et cette réponse pour les comportements divergents réels sur les navigateurs actuels.
Jo Liss
2
@JoLiss Merci pour ça! Mais veuillez noter que je n'ai jamais revendiqué la préservation de l'ordre entre le code et l'objet compilé. Je réclamais la préservation de l'ordre des propriétés dont les valeurs sont remplacées sur place. C'était la clé de ma solution: utiliser un mixin pour simplement écraser les valeurs des propriétés. En supposant que les implémentations choisissent généralement d'utiliser une sorte de hashmap, le remplacement des seules valeurs devrait préserver l'ordre des clés. C'est en fait exactement cela que j'avais testé dans différents navigateurs.
Ates Goral
1
@AtesGoral: est-il possible de rendre cette contrainte un peu plus explicite (gras, ...). La plupart des gens font simplement du copier-coller sans lire le texte qui l'entoure ...
Willem Van Onsem
21

Dans Node.js, vous pouvez utiliser son native require("assert").deepStrictEqual. Plus d'informations: http://nodejs.org/api/assert.html

Par exemple:

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

Un autre exemple qui renvoie true/ falseau lieu de renvoyer des erreurs:

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
la source
Chaia cette fonctionnalité aussi. Dans son cas, vous utiliserez:var foo = { a: 1 }; var bar = { a: 1 }; expect(foo).to.deep.equal(bar); // true;
Folusho Oladipo
Certaines versions de Node.js sont définies error.namesur "AssertionError [ERR_ASSERTION]". Dans ce cas, je remplacerais l'instruction if par if (error.code === 'ERR_ASSERTION') {.
Knute Knudsen
Je n'avais aucune idée de deepStrictEqualla voie à suivre. J'avais fait trembler mon cerveau en essayant de comprendre pourquoi strictEqualne fonctionnait pas. Fantastique.
NetOperator Wibby
19

Solutions les plus simples et logiques pour comparer tout comme Object, Array, String, Int ...

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

Remarque:

  • vous devez remplacer val1et val2avec votre objet
  • pour l'objet, vous devez trier (par clé) récursivement pour les deux objets latéraux
Pratik Bhalodiya
la source
6
Je suppose que cela ne fonctionnera pas dans de nombreux cas parce que l'ordre des clés dans les objets n'a pas d'importance - à moins JSON.stringifyqu'une réorganisation alphabétique? (Ce que je ne trouve pas documenté .)
Bram Vanroy
yup vous avez raison ... pour l'objet, vous devez trier récursivement pour les deux objets latéraux
Pratik Bhalodiya
2
Cela ne fonctionne pas pour les objets avec des références circulaires
Nate-Bit Int
13

J'utilise cette comparablefonction pour produire des copies de mes objets qui sont comparables au 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>

Pratique dans les tests (la plupart des frameworks de test ont une isfonction). Par exemple

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

Si une différence est détectée, les chaînes sont enregistrées, ce qui rend les différences 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}}.
foc
la source
1
bonne idée (dans mon cas, les objets à comparer juste une paire clé / valeur, pas de choses spéciales)
mech
10

Heres est une solution dans ES6 / ES2015 utilisant une approche de style fonctionnel:

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

démo disponible ici

Alan R. Soares
la source
Ne fonctionne pas si l'ordre des clés d'objet a changé.
Isaac Pak
9

Je ne sais pas si quelqu'un a posté quelque chose de similaire, mais voici une fonction que j'ai créée pour vérifier les égalités d'objets.

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

En outre, il est récursif, il peut donc également vérifier une profonde égalité, si c'est ce que vous appelez.

Zac
la source
petite correction: avant de parcourir chaque accessoire en a et b, ajoutez cette vérification si (Object.getOwnPropertyNames (a) .length! == Object.getOwnPropertyNames (b) .length) renvoie false
Hith
1
il est évident qu'un bon vérificateur d'égalité doit être récursif. Je pense qu'une de ces réponses récursives devrait être la bonne réponse. La réponse acceptée ne donne pas de code et cela n'aide pas
canbax
9

Pour ceux d'entre vous qui utilisent NodeJS, il existe une méthode pratique appelée isDeepStrictEqualsur la bibliothèque native Util qui peut y parvenir.

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
la source
ses performances sont supposées être bonnes. Pas de soucis. Même moi, je l'utilise également dans des scénarios complexes. Comme lorsque nous l'utilisons, nous n'avons pas à nous inquiéter si la propriété de l'objet porte un objet ou un tableau. Json.Stringify le fait de toute façon et la comparaison des chaînes en javascript n'est pas un gros problème
TrickOrTreat
6

ES6: Voici le code minimum que je pourrais faire. Il effectue une comparaison approfondie récursivement en stringifiant tous les objets, la seule limitation est qu'aucune méthode ou aucun symbole n'est comparé.

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
la source
C'est une réponse entièrement fonctionnelle, merci @Adriano Spadoni. Savez-vous comment obtenir la clé / l'attribut qui a été modifié? Merci,
digitai
1
salut @digital, si vous avez besoin de touches différentes, ce n'est pas la fonction idéale. Vérifiez l'autre réponse et utilisez-en une avec une boucle à travers les objets.
Adriano Spadoni
5

vous pouvez utiliser _.isEqual(obj1, obj2) partir de la bibliothèque underscore.js.

Voici un exemple:

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

Voir la documentation officielle ici: http://underscorejs.org/#isEqual

Bentaiba Miled Basma
la source
5

En supposant que l'ordre des propriétés dans l'objet n'est pas modifié.

JSON.stringify () fonctionne pour les deux types d'objets profonds et non profonds, pas très sûr des aspects de performance:

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
la source
1
Cela ne fait pas ce que souhaite OP, car il ne correspondra que si les deux objets ont tous les mêmes clés, ce qu'ils déclarent ne pas avoir. Il faudrait également que les clés soient dans le même ordre, ce qui n'est pas non plus vraiment raisonnable.
SpeedOfRound
1
Quelles sont les propriétés sont dans un ordre différent ??? Non, ce n'est pas une bonne méthode
Vishal Sakaria
4

Une solution simple à ce problème que beaucoup de gens ne réalisent pas est de trier les chaînes JSON (par caractère). C'est aussi généralement plus rapide que les autres solutions mentionnées ici:

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

Une autre chose utile à propos de cette méthode est que vous pouvez filtrer les comparaisons en passant une fonction "replacer" aux fonctions JSON.stringify ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON / stringify # Example_of_using_replacer_parameter ). Les éléments suivants comparent uniquement toutes les clés d'objets nommées "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
la source
1
Oh, j'ai aussi oublié, mais la fonction peut être accélérée en testant d'abord l'égalisation et la mise en mémoire anticipée des objets s'ils sont le même objet: if (obj1 === obj2) return true;
th317erd
11
areEqual({a: 'b'}, {b: 'a'})obtient truealors?
okm
Oui, j'ai réalisé après avoir posté que cette "solution" avait des problèmes. Il a besoin d'un peu plus de travail dans l'algorithme de tri pour fonctionner correctement.
th317erd
4

Je voulais juste contribuer ma version de comparaison d'objets en utilisant certaines fonctionnalités d'es6. Il ne tient pas compte d'une commande. Après avoir converti tous les if / else en ternaire, je suis venu avec ce qui suit:

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
la source
3

Ayant besoin d'une fonction de comparaison d'objets plus générique que celle publiée, j'ai concocté ce qui suit. Critique appréciée ...

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
la source
2
J'ai fini par utiliser une variante de cela. Merci pour l'idée de compter les membres!
NateS
2
Soyez très prudent Object.prototypelorsque vous modifiez - dans la grande majorité des cas, il n'est pas conseillé (des ajouts apparaissent dans toutes les boucles for..in, par exemple). Peut-être envisager Object.equals = function(aObj, bObj) {...}?
Roy Tinker
3

Si vous comparez des objets JSON, vous pouvez utiliser https://github.com/mirek/node-rus-diff

npm install rus-diff

Usage:

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 deux objets sont différents, un {$rename:{...}, $unset:{...}, $set:{...}}objet similaire compatible MongoDB est renvoyé.

Mirek Rusin
la source
3

J'ai fait face au même problème et j'ai décidé d'écrire ma propre solution. Mais parce que je veux également comparer des tableaux avec des objets et vice-versa, j'ai conçu une solution générique. J'ai décidé d'ajouter les fonctions au prototype, mais on peut facilement les réécrire en fonctions autonomes. Voici le code:

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

Cet algorithme est divisé en deux parties; La fonction equals elle-même et une fonction pour trouver l'index numérique d'une propriété dans un tableau / objet. La fonction find est uniquement nécessaire car indexof ne trouve que des nombres et des chaînes et aucun objet.

On peut l'appeler ainsi:

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

La fonction retourne vrai ou faux, dans ce cas vrai. L'algorithme permet également de comparer des objets très complexes:

({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"]})

L'exemple supérieur retournera vrai, même si les propriétés ont un ordre différent. Un petit détail à surveiller: Ce code vérifie également le même type de deux variables, donc "3" n'est pas le même que 3.

Sir_baaron
la source
3

Je vois des réponses de code de spaghetti. Sans utiliser de bibliothèques tierces, c'est très simple.

Triez d'abord les deux objets en saisissant leurs noms de clé.

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)

Ensuite, utilisez simplement une chaîne pour les comparer.

JSON.stringify(objectOne) === JSON.stringify(objectTwo)
Oliver Dixon
la source
Cela ne fonctionne pas non plus pour la copie en profondeur, il n'a qu'une seule profondeur d'itération.
andras
Je pense que @andras signifie que vous devez trier récursivement les clés des objets imbriqués.
Davi Lima
2

Je déconseille le hachage ou la sérialisation (comme le suggère la solution JSON). Si vous devez tester si deux objets sont égaux, vous devez définir ce que signifie égal. Il se peut que tous les membres de données des deux objets correspondent, ou que les emplacements de mémoire doivent correspondre (ce qui signifie que les deux variables référencent le même objet en mémoire), ou que seul un membre de données de chaque objet doit correspondre.

Récemment, j'ai développé un objet dont le constructeur crée un nouvel identifiant (à partir de 1 et incrémentant de 1) chaque fois qu'une instance est créée. Cet objet a une fonction isEqual qui compare cette valeur id avec la valeur id d'un autre objet et renvoie true si elles correspondent.

Dans ce cas, j'ai défini "égal" comme signifiant la correspondance des valeurs id. Étant donné que chaque instance a un identifiant unique, cela pourrait être utilisé pour renforcer l'idée que les objets correspondants occupent également le même emplacement de mémoire. Bien que ce ne soit pas nécessaire.

Bernard Igiri
la source
2

Il est utile de considérer deux objets égaux s'ils ont toutes les mêmes valeurs pour toutes les propriétés et récursivement pour tous les objets et tableaux imbriqués. Je considère également les deux objets suivants égaux:

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

De même, les tableaux peuvent avoir des éléments "manquants" et des éléments non définis. Je les traiterais également de la même manière:

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

Une fonction qui met en œuvre cette définition de l'égalité:

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

Code source (y compris les fonctions d'assistance, generalType et uniqueArray): Unit Test et Test Runner ici .

mckoss
la source
2

Je fais les hypothèses suivantes avec cette fonction:

  1. Vous contrôlez les objets que vous comparez et vous n'avez que des valeurs primitives (c'est-à-dire pas des objets imbriqués, des fonctions, etc.).
  2. Votre navigateur prend en charge Object.keys .

Cela devrait être considéré comme la démonstration d'une stratégie 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
la source
2

C'est un ajout pour tout ce qui précède, pas un remplacement. Si vous devez comparer rapidement des objets peu profonds sans avoir besoin de vérifier les cas récursifs supplémentaires. Voici une photo.

Cela se compare à: 1) égalité du nombre de propriétés propres, 2) égalité des noms de clé, 3) si bCompareValues ​​== true, égalité des valeurs de propriété correspondantes et de leurs types (triple égalité)

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
la source
2

Pour comparer des clés pour des instances d'objets de paires clé / valeur simples, j'utilise:

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

Une fois les clés comparées, un simple supplément for..in boucle suffit.

La complexité est O (N * N) avec N est le nombre de clés.

J'espère / devine que les objets que je définis ne contiendront pas plus de 1000 propriétés ...

Hefeust CORTES
la source
2

Je sais que c'est un peu vieux, mais je voudrais ajouter une solution que j'ai trouvée pour ce problème. J'avais un objet et je voulais savoir quand ses données changeaient. "quelque chose de similaire à Object.observe" et ce que j'ai fait était:

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

Ceci peut être dupliqué ici et créer un autre ensemble de tableaux pour comparer les valeurs et les clés. C'est très simple car ce sont maintenant des tableaux et retourneront faux si les objets ont des tailles différentes.

inoabrien
la source
1
Cela reviendra toujours false, car les tableaux ne se comparent pas par valeur, par exemple [1,2] != [1,2].
foc