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


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

Question de débordement 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 y en a solution équivalente.


479
2017-10-14 13:41


origine


Réponses:


La réponse courte

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

La longue réponse

Le concept est celui d'une méthode Equals qui compare deux instances différentes d'un objet pour indiquer si elles sont égales au niveau de la valeur. Cependant, il appartient au type spécifique de définir comment Equals méthode devrait être mise en œuvre. 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, c n'est pas vraiment important de déterminer si deux instances de MyClass sont égales, seulement a et b sont importants. Dans certains cas c peut varier d'une instance à l'autre et ne pas être significatif pendant 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 doit disposer d'un moyen de déterminer l'égalité.

Ce qui complique encore les choses, c'est qu'en JavaScript, la distinction entre les données et la méthode est floue.

Un objet peut référencer une méthode qui doit être appelée en tant que gestionnaire d'événement, et cela ne sera probablement pas considéré comme faisant partie de son «état de valeur». Alors qu'une autre fonction peut être affectée à un autre objet qui effectue un calcul important, ce qui rend 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 des méthodes prototypes existantes est surchargée par une autre fonction? Pourrait-il encore être considéré comme égal à une autre instance qu'il est par ailleurs identique? Cette question ne peut être résolue que dans chaque cas spécifique pour chaque type.

Comme indiqué précédemment, l'exception serait un objet strictement sans caractère. 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?


141
2017-10-14 14:48



Pourquoi réinventer la roue? Donner Lodash un essai. Il a un certain nombre de fonctions indispensables telles que est égal().

_.isEqual(object, other);

Il faudra vérifier la force brute de chaque valeur de clé - tout comme les autres exemples sur cette page - en utilisant ECMAScript 5 et les optimisations natives si elles sont disponibles dans le navigateur.

Note: Auparavant, cette réponse était recommandée Underscore.js, mais lodash a fait un meilleur travail pour corriger les bugs et résoudre les problèmes avec cohérence.


414
2017-07-07 19:35



L'opérateur d'égalité par défaut en JavaScript pour les objets donne une valeur vraie lorsqu'ils font référence 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 devez ajouter equals(other) méthode, ou quelque chose comme ça à vos classes et les spécificités de votre domaine de problème déterminera 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

136
2018-05-20 03:48



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

  • angular.equals - Voir le docs ou la source pour plus d'informations sur cette méthode. Il fait une comparaison profonde sur les tableaux aussi.
  • Ember.js isEqual - Voir le docs ou la source pour plus d'informations sur cette méthode. Il ne fait pas une comparaison profonde 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>


66
2017-09-27 16:13



Ceci est ma version Il utilise de nouvelles Object.keys fonctionnalité 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 } }));


44
2018-05-28 09:28



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

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, cela pose problème pour diverses raisons. Dans presque tous les cas, vous voudrez trouver une solution plus robuste.


43
2017-10-14 14:11



Si vous avez une fonction de copie profonde à portée de main, vous pouvez utiliser l'astuce suivante pour encore utilisation JSON.stringify en faisant correspondre 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émonstration http://jsfiddle.net/CU3vb/3/

Raisonnement:

Depuis les propriétés de obj1 sont copiés au clone un par un, leur ordre dans le clone sera préservé. Et quand les propriétés de obj2 sont copiés sur le clone, puisque les propriétés déjà existantes dans obj1 seront simplement écrasés, leurs ordres dans le clone seront conservés.


19
2018-06-14 19:20



Fonctionnelle courte deepEqual la mise en oeuvre:

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

modifier: version 2, utilisant la suggestion de jib et les fonctions de flèche 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);
}

18
2017-10-03 11:36