Wie bestimme ich die Gleichheit für zwei JavaScript-Objekte?


667

Ein strikter Gleichheits - Operator werden Ihnen sagen , wenn zwei Objekttypen gleich sind. Gibt es jedoch eine Möglichkeit, festzustellen, ob zwei Objekte gleich sind, ähnlich wie beim Hash-Code Wert in Java?

Frage zum Stapelüberlauf Gibt es eine HashCode-Funktion in JavaScript? ähnelt dieser Frage, erfordert jedoch eine akademischere Antwort. Das obige Szenario zeigt, warum es notwendig wäre, eine zu haben, und ich frage mich, ob es eine gleichwertige Lösung gibt .


3
Schauen Sie sich auch diese Frage an stackoverflow.com/q/1068834/1671639
Praveen

37
Beachten Sie, dass, auch in Java, a.hashCode() == b.hashCode()ist nicht bedeuten , dass agleich b. Es ist eine notwendige Bedingung, keine ausreichende.
Heinzi


2
Wenn Sie Objekte in Ihrem Code vergleichen MÜSSEN, schreiben Sie Ihren Code wahrscheinlich falsch. Die bessere Frage könnte sein: "Wie kann ich diesen Code schreiben, damit ich keine Objekte vergleichen muss?"
th317erd

3
@ th317erd können Sie sich bitte erklären? ...
El Mac

Antworten:


175

Die kurze Antwort

Die einfache Antwort lautet: Nein, es gibt keine generischen Mittel, um festzustellen, ob ein Objekt einem anderen in dem von Ihnen gemeinten Sinne gleich ist. Die Ausnahme ist, wenn Sie streng daran denken, dass ein Objekt typenlos ist.

Die lange Antwort

Das Konzept ist das einer Equals-Methode, bei der zwei verschiedene Instanzen eines Objekts verglichen werden, um anzuzeigen, ob sie auf einer Wertebene gleich sind. Es ist jedoch Sache des jeweiligen Typs, zu definieren, wie eine EqualsMethode implementiert werden soll. Ein iterativer Vergleich von Attributen mit primitiven Werten reicht möglicherweise nicht aus. Es kann durchaus Attribute geben, die nicht als Teil des Objektwerts betrachtet werden sollen. Zum Beispiel,

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

In diesem obigen Fall cist es nicht wirklich wichtig zu bestimmen, ob zwei Instanzen von MyClass gleich sind, nur aundb sind wichtig. In manchen Fällenc kann dies zwischen den Instanzen variieren und ist beim Vergleich dennoch nicht signifikant.

Beachten Sie, dass dieses Problem auftritt, wenn Mitglieder selbst auch Instanzen eines Typs sein können und diese alle über ein Mittel zur Bestimmung der Gleichheit verfügen müssen.

Erschwerend kommt hinzu, dass in JavaScript die Unterscheidung zwischen Daten und Methode unscharf ist.

Ein Objekt kann auf eine Methode verweisen, die als Ereignishandler aufgerufen werden soll, und dies wird wahrscheinlich nicht als Teil seines 'Wertezustands' betrachtet. Während einem anderen Objekt durchaus eine Funktion zugewiesen werden kann, die eine wichtige Berechnung durchführt und dadurch diese Instanz von anderen unterscheidet, nur weil sie auf eine andere Funktion verweist.

Was ist mit einem Objekt, bei dem eine der vorhandenen Prototypmethoden von einer anderen Funktion überschrieben wird? Könnte es immer noch als gleichwertig mit einer anderen Instanz angesehen werden, dass es ansonsten identisch ist? Diese Frage kann nur in jedem Einzelfall für jeden Typ beantwortet werden.

Wie bereits erwähnt, wäre die Ausnahme ein streng typenloses Objekt. In diesem Fall ist die einzig sinnvolle Wahl ein iterativer und rekursiver Vergleich jedes Mitglieds. Selbst dann muss man sich fragen, was der 'Wert' einer Funktion ist.


174
Wenn Sie Unterstrich verwenden, können Sie einfach tun_.isEqual(obj1, obj2);
chovy

12
@Harsh, die Antwort gab keine Lösung, weil es keine gibt. Selbst in Java gibt es keine Silberkugel für den Vergleich der Objektgleichheit, und die korrekte Implementierung der .equalsMethode ist nicht trivial, weshalb es in Effective Java ein solches Thema gibt .
lcn

3
@ Kumar Harsh, Was zwei Objekte gleich macht, ist sehr anwendungsspezifisch; Nicht jede Eigenschaft eines Objekts sollte unbedingt berücksichtigt werden, daher ist es auch keine konkrete Lösung, jede Eigenschaft eines Objekts brutal zu erzwingen.
Sethro

googelte javascript equality object, bekam tl; dr Antwort, nahm Einzeiler von @chovy Kommentar. Vielen Dank
Andrea

Wenn Sie eckig verwenden, haben Sieangular.equals
Bootscodierer

506

Warum das Rad neu erfinden? Probieren Sie Lodash aus . Es hat eine Reihe von Must-Have-Funktionen wie isEqual () .

_.isEqual(object, other);

Jeder Schlüsselwert wird - genau wie in den anderen Beispielen auf dieser Seite - mithilfe von ECMAScript 5 und nativen Optimierungen brutal überprüft, sofern diese im Browser verfügbar sind.

Hinweis: Früher empfahl diese Antwort Underscore.js , aber lodash hat es besser gemacht, Fehler zu beheben und Probleme mit Konsistenz zu beheben.


27
Die isEqual-Funktion von Underscore ist sehr nett (aber Sie müssen ihre Bibliothek aufrufen, um sie zu verwenden - ungefähr 3K gzipped).
McKoss

29
Wenn Sie sich ansehen, was Ihnen sonst noch ein Unterstrich gibt, werden Sie es nicht bereuen, ihn
eingezogen zu haben

6
Auch wenn Sie es sich nicht leisten können, einen Unterstrich als Abhängigkeit zu haben, ziehen Sie die Funktion isEqual heraus, erfüllen Sie die Lizenzanforderungen und fahren Sie fort. Es ist mit Abstand der umfassendste Gleichheitstest, der für den Stackoverflow erwähnt wird.
Dale Anderson

7
Es gibt eine Gabelung mit Unterstrich namens LoDash, und dieser Autor ist sehr besorgt über solche Konsistenzprobleme. Testen Sie mit LoDash und sehen Sie, was Sie bekommen.
CoolAJ86

6
@ McKoss Sie können das Standalone-Modul verwenden, wenn Sie nicht die gesamte Bibliothek npmjs.com/package/lodash.isequal
Rob Fox

161

Der Standard-Gleichheitsoperator in JavaScript für Objekte ergibt true, wenn sie sich auf denselben Speicherort im Speicher beziehen.

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

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

Wenn Sie einen anderen Gleichheitsoperator benötigen, müssen equals(other)Sie Ihren Klassen eine Methode oder ähnliches hinzufügen , und die Besonderheiten Ihrer Problemdomäne bestimmen, was genau dies bedeutet.

Hier ist ein Spielkartenbeispiel:

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

Wenn die Objekte in eine JSON-Zeichenfolge konvertiert werden können, ist eine Funktion equals () einfach.
Scotts

3
@scotts Nicht immer. Das Konvertieren von Objekten in JSON und das Vergleichen von Zeichenfolgen kann für komplexe Objekte in engen Schleifen rechenintensiv werden. Für einfache Objekte spielt es wahrscheinlich keine Rolle, aber in Wirklichkeit hängt es wirklich von Ihrer spezifischen Situation ab. Eine korrekte Lösung kann so einfach sein wie das Vergleichen von Objekt-IDs oder das Überprüfen jeder Eigenschaft, aber ihre Richtigkeit wird vollständig von der Problemdomäne bestimmt.
Daniel X Moore

Sollten wir nicht auch den Datentyp vergleichen?! return other.rank === this.rank && other.suit === this.suit;
Devsathish

1
@devsathish wahrscheinlich nicht. In JavaScript sind Typen ziemlich schnell und locker, aber wenn in Ihrer Domain Typen wichtig sind, möchten Sie möglicherweise auch Typen überprüfen.
Daniel X Moore

7
@scotts Ein weiteres Problem bei der Konvertierung in JSON besteht darin, dass die Reihenfolge der Eigenschaften in der Zeichenfolge erheblich wird. {x:1, y:2}! =={y:2, x:1}
Stijn de Witt

81

Wenn Sie in AngularJS arbeiten , ermittelt die angular.equalsFunktion, ob zwei Objekte gleich sind. In Ember.js verwenden isEqual.

  • angular.equals- Weitere Informationen zu dieser Methode finden Sie in den Dokumenten oder in der Quelle . Es macht auch einen tiefen Vergleich bei Arrays.
  • Ember.js isEqual- Weitere Informationen zu dieser Methode finden Sie in den Dokumenten oder in der Quelle . Es wird kein tiefer Vergleich für Arrays durchgeführt.

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

Das ist meine Version. Es verwendet die neue Funktion Object.keys , die in ES5 eingeführt wird, sowie Ideen / Tests von + , + und + :

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


objectEquals([1,2,undefined],[1,2])Rückkehrtrue
Roy Tinker

objectEquals([1,2,3],{0:1,1:2,2:3})gibt auch zurück true- zB gibt es keine Typprüfung, nur Schlüssel- / Wertprüfung.
Roy Tinker

objectEquals(new Date(1234),1234)kehrt zurücktrue
Roy Tinker

1
if (x.constructor! == y.constructor) {return false; } Dies würde beim Vergleich von zwei 'neuen Zeichenfolgen (' a ')' in verschiedenen Fenstern unterbrochen. Für die Wertgleichheit müssten Sie überprüfen, ob String.isString für beide Objekte gilt, und dann eine lose Gleichheitsprüfung 'a == b' verwenden.
Triynko

1
Es gibt einen großen Unterschied zwischen "Wert" -Gleichheit und "strenger" Gleichheit und sie sollten nicht auf die gleiche Weise implementiert werden. Die Wertgleichheit sollte sich nicht um Typen kümmern, abgesehen von der Grundstruktur, die eines dieser 4 ist: 'Objekt' (dh eine Sammlung von Schlüssel / Wert-Paaren), 'Zahl', 'Zeichenfolge' oder 'Array'. Das ist es. Alles, was keine Zahl, Zeichenfolge oder kein Array ist, sollte als Satz von Schlüssel / Wert-Paaren verglichen werden, unabhängig davon, was der Konstruktor ist (fensterübergreifend). Wenn Sie Objekte vergleichen, setzen Sie den Wert von Literalzahlen und Instanzen von Number gleich, aber zwingen Sie Zeichenfolgen nicht zu Zahlen.
Triynko

50

Wenn Sie eine JSON-Bibliothek verwenden, können Sie jedes Objekt als JSON codieren und dann die resultierenden Zeichenfolgen auf Gleichheit vergleichen.

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

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

HINWEIS: Obwohl diese Antwort in vielen Fällen funktioniert, ist sie aus verschiedenen Gründen problematisch, wie mehrere Personen in den Kommentaren hervorgehoben haben. In fast allen Fällen möchten Sie eine robustere Lösung finden.


91
Interessant, aber meiner Meinung nach etwas knifflig. Können Sie beispielsweise zu 100% garantieren, dass die Objekteigenschaften immer in derselben Reihenfolge generiert werden?
Guido

25
Das ist eine gute Frage und wirft eine andere auf, ob zwei Objekte mit denselben Eigenschaften in unterschiedlichen Reihenfolgen wirklich gleich sind oder nicht. Kommt darauf an, was du mit gleich meinst, denke ich.
Joel Anair

11
Beachten Sie, dass die meisten Encoder und Stringifier Funktionen ignorieren und nicht endliche Zahlen wie NaN in Null konvertieren.
Stephen Belanger

4
Ich stimme Guido zu, die Reihenfolge der Eigenschaften ist wichtig und kann nicht garantiert werden. @JoelAnair, ich denke, zwei Objekte mit denselben Eigenschaften in unterschiedlichen Reihenfolgen sollten als gleich angesehen werden, wenn der Wert der Eigenschaften gleich ist.
Juzer Ali

5
Dies könnte mit einem alternativen JSON-Stringifizierer funktionieren, der Objektschlüssel konsistent sortiert.
Roy Tinker

40

Kurze funktionale deepEqualImplementierung:

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

Bearbeiten : Version 2 mit Jibs Vorschlag und ES6-Pfeilfunktionen:

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

5
Sie könnten ersetzen reducemit everyzu vereinfachen.
Fock

1
@nonkertompf sicher, dass er : Object.keys(x).every(key => deepEqual(x[key], y[key])).
Fock

2
Dies schlägt fehl, wenn Sie zwei Daten vergleichen
Greg

3
deepEqual ({}, []) gibt true zurück
AlexMorley-Finch

3
ja, wenn Sie für eine solche Ecke Fall kümmern, hässliche Lösung zu ersetzen ist : (x === y)mit: (x === y && (x != null && y != null || x.constructor === y.constructor))
atmin

22

Versuchen Sie zu testen, ob zwei Objekte gleich sind? dh: ihre Eigenschaften sind gleich?

Wenn dies der Fall ist, haben Sie wahrscheinlich diese Situation bemerkt:

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

Möglicherweise müssen Sie Folgendes tun:

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

Offensichtlich könnte diese Funktion einiges an Optimierung und die Möglichkeit einer gründlichen Überprüfung (zur Behandlung verschachtelter Objekte) vertragen: var a = { foo : { fu : "bar" } } aber Sie haben die Idee.

Wie FOR hervorhob, müssen Sie dies möglicherweise für Ihre eigenen Zwecke anpassen, z. B.: Verschiedene Klassen haben möglicherweise unterschiedliche Definitionen von "gleich". Wenn Sie nur mit einfachen Objekten arbeiten, kann das oben Genannte ausreichen, andernfalls kann eine benutzerdefinierte MyClass.equals()Funktion der richtige Weg sein.


Es ist eine lange Methode, aber sie testet die Objekte vollständig, ohne Annahmen über die Reihenfolge der Eigenschaften in jedem Objekt zu treffen.
briancollins081

22

Wenn Sie eine tiefe Kopierfunktion zur Hand haben, können Sie den folgenden Trick verwenden , um noch verwendet werden, JSON.stringifywährend die Reihenfolge der Objekte in dieser Auswahl:

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

Demo: http://jsfiddle.net/CU3vb/3/

Begründung:

Da die Eigenschaften von obj1einzeln in den Klon kopiert werden, bleibt ihre Reihenfolge im Klon erhalten. Und wenn die Eigenschaften von obj2in den Klon kopiert obj1werden, werden ihre Ordnungen im Klon beibehalten , da bereits vorhandene Eigenschaften einfach überschrieben werden.


11
Ich glaube nicht, dass die Auftragserhaltung für alle Browser / Engines garantiert ist.
Jo Liss

@ JoLiss Zitate benötigt;) Ich erinnere mich, dass ich dies in mehreren Browsern getestet habe, um konsistente Ergebnisse zu erhalten. Aber natürlich kann niemand garantieren, dass das Verhalten in zukünftigen Browsern / Engines gleich bleibt. Dies ist bestenfalls ein Trick (wie bereits in der Antwort erwähnt), und ich wollte nicht, dass es ein todsicherer Weg ist, Objekte zu vergleichen.
Ates Goral

1
Sicher, hier sind einige Hinweise: Die ECMAScript-Spezifikation besagt, dass das Objekt "ungeordnet" ist ; und diese Antwort für tatsächlich divergierendes Verhalten in aktuellen Browsern.
Jo Liss

2
@ JoLiss Danke dafür! Bitte beachten Sie jedoch, dass ich nie die Aufrechterhaltung der Ordnung zwischen Code und kompiliertem Objekt beansprucht habe. Ich habe behauptet, die Reihenfolge der Eigenschaften beizubehalten, deren Werte an Ort und Stelle ersetzt werden. Das war der Schlüssel zu meiner Lösung: Verwenden Sie ein Mixin, um nur Eigenschaftswerte zu überschreiben. Unter der Annahme, dass Implementierungen im Allgemeinen eine Art Hashmap verwenden, sollte beim Ersetzen nur von Werten die Reihenfolge der Schlüssel beibehalten werden. Genau das hatte ich in verschiedenen Browsern getestet.
Ates Goral

1
@AtesGoral: Ist es möglich, diese Einschränkung etwas expliziter zu gestalten (Fettdruck, ...). Die meisten Leute kopieren und fügen einfach ein, ohne den Text zu lesen ...
Willem Van Onsem

21

In Node.js können Sie das native verwenden require("assert").deepStrictEqual. Weitere Informationen: http://nodejs.org/api/assert.html

Zum Beispiel:

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

Ein weiteres Beispiel, das true/ falseanstelle von Fehlern zurückgibt :

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

Chaihat diese Funktion auch. In seinem Fall werden Sie verwenden:var foo = { a: 1 }; var bar = { a: 1 }; expect(foo).to.deep.equal(bar); // true;
Folusho Oladipo

Einige Versionen von Node.js gesetzt error.namezu "AssertionError [ERR_ASSERTION]". In diesem Fall würde ich die if-Anweisung durch ersetzen if (error.code === 'ERR_ASSERTION') {.
Knute Knudsen

Ich hatte keine Ahnung, deepStrictEqualwie es weitergehen sollte. Ich hatte mir den Kopf zerbrochen, um herauszufinden, warum strictEquales nicht funktionierte. Fantastisch.
NetOperator Wibby

19

Einfachste und logischste Lösungen zum Vergleichen von Objekten wie Objekt, Array, String, Int ...

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

Hinweis:

  • Sie müssen val1und val2mit Ihrem Objekt ersetzen
  • Für das Objekt müssen Sie für beide Seitenobjekte rekursiv (nach Schlüssel) sortieren

6
Ich gehe davon aus, dass dies in vielen Fällen nicht funktioniert, da die Reihenfolge der Schlüssel in Objekten keine Rolle spielt - es sei denn JSON.stringify, eine alphabetische Neuordnung erfolgt? (Was ich nicht dokumentiert finden kann .)
Bram Vanroy

yup Sie haben Recht ... für das Objekt müssen Sie rekursiv für beide
Seitenobjekte sortieren

2
Dies funktioniert nicht für Objekte mit Zirkelverweisen
Nate-Bit Int

13

Ich verwende diese comparableFunktion, um Kopien meiner Objekte zu erstellen, die mit JSON vergleichbar sind:

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>

Ist praktisch in Tests (die meisten Test-Frameworks haben eine isFunktion). Z.B

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

Wenn ein Unterschied festgestellt wird, werden Zeichenfolgen protokolliert, wodurch Unterschiede erkennbar werden:

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

1
gute Idee (in meinem Fall die zu vergleichenden Objekte ein nur Schlüssel / Wert-Paar, keine besonderen Dinge)
Mech

10

Hier ist eine Lösung in ES6 / ES2015, die einen funktionalen Ansatz verwendet:

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

Demo hier verfügbar


Funktioniert nicht, wenn sich die Reihenfolge der Objektschlüssel geändert hat.
Isaac Pak

9

Ich weiß nicht, ob jemand etwas Ähnliches gepostet hat, aber hier ist eine Funktion, die ich gemacht habe, um nach Objektgleichheiten zu suchen.

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

Außerdem ist es rekursiv, sodass es auch auf tiefe Gleichheit prüfen kann, wenn Sie es so nennen.


kleine Korrektur: Bevor Sie die einzelnen Requisiten in a und b durchgehen, fügen Sie diese Prüfung hinzu, wenn (Object.getOwnPropertyNames (a) .length! == Object.getOwnPropertyNames (b) .length) false
zurückgibt

1
Es ist offensichtlich, dass der richtige Gleichheitsprüfer rekursiv sein muss. Ich denke, eine dieser rekursiven Antworten sollte die richtige sein. Die akzeptierte Antwort gibt keinen Code und es hilft nicht
canbax

9

Für diejenigen unter Ihnen, die NodeJS verwenden, gibt es eine praktische Methode, die isDeepStrictEqualfür die native Util-Bibliothek aufgerufen wird, um dies zu erreichen.

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


seine Leistung soll gut sein. Keine Sorge. Auch ich benutze dies auch in komplexen Szenarien. Wie wenn wir dies verwenden, müssen wir uns keine Sorgen machen, ob die Eigenschaft eines Objekts ein Objekt oder ein Array trägt. Json.Stringify macht es trotzdem zu einem String und der Vergleich von Strings in Javascript ist keine große Sache
TrickOrTreat

6

ES6: Der Mindestcode, den ich dafür bekommen könnte, ist dieser. Es führt einen tiefen rekursiven Vergleich durch, indem alle Objekte stringifiziert werden. Die einzige Einschränkung besteht darin, dass keine Methoden oder Symbole verglichen werden.

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


Dies ist eine voll funktionsfähige Antwort, danke @Adriano Spadoni. Wissen Sie, wie ich den Schlüssel / das Attribut erhalten kann, das / das geändert wurde? Danke,
digitai

1
hi @digital, wenn du brauchst, welche Tasten unterschiedlich sind, ist dies nicht die ideale Funktion. Überprüfen Sie die andere Antwort und verwenden Sie eine mit einer Schleife durch die Objekte.
Adriano Spadoni

5

Sie können _.isEqual(obj1, obj2)aus der Bibliothek underscore.js verwenden.

Hier ist ein Beispiel:

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

Die offizielle Dokumentation finden Sie hier: http://underscorejs.org/#isEqual


5

Angenommen, die Reihenfolge der Eigenschaften im Objekt wird nicht geändert.

JSON.stringify () funktioniert für beide Objekttypen tief und nicht tief, wobei die Leistungsaspekte nicht sehr sicher sind:

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


1
Dies macht nicht das, was OP will, da es nur übereinstimmt, wenn beide Objekte dieselben Schlüssel haben, von denen sie angeben, dass sie dies nicht tun. Es würde auch erfordern, dass die Schlüssel in derselben Reihenfolge sind, was auch nicht wirklich vernünftig ist.
SpeedOfRound

1
Was ist die Eigenschaften sind in unterschiedlicher Reihenfolge ??? Nein, keine gute Methode
Vishal Sakaria

4

Eine einfache Lösung für dieses Problem, die viele Menschen nicht erkennen, besteht darin, die JSON-Zeichenfolgen (pro Zeichen) zu sortieren. Dies ist normalerweise auch schneller als die anderen hier genannten Lösungen:

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

Eine weitere nützliche Sache bei dieser Methode ist, dass Sie Vergleiche filtern können, indem Sie eine "Ersetzer" -Funktion an die JSON.stringify-Funktionen übergeben ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON) / stringify # Example_of_using_replacer_parameter ). Im Folgenden werden nur alle Objektschlüssel mit dem Namen "derp" verglichen:

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

1
Oh, ich habe es auch vergessen, aber die Funktion kann beschleunigt werden, indem zuerst ein Objekt getestet wird, das gleich ist, und frühzeitig gerettet wird, wenn es sich um dasselbe Objekt handelt: if (obj1 === obj2) gibt true zurück;
th317erd

11
areEqual({a: 'b'}, {b: 'a'})bekommt truedann?
Okm

Ja, ich habe nach dem Posten festgestellt, dass diese "Lösung" Probleme hat. Es erfordert etwas mehr Arbeit im Sortieralgorithmus, um tatsächlich richtig zu funktionieren.
th317erd

4

Ich wollte nur meine Version des Objektvergleichs mit einigen es6-Funktionen beitragen. Eine Bestellung wird nicht berücksichtigt. Nachdem ich alle if / else's in ternary konvertiert habe, bin ich mit folgendem Ergebnis gekommen:

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;

        }
    )
}

3

Ich brauchte eine allgemeinere Objektvergleichsfunktion als veröffentlicht und habe mir Folgendes ausgedacht. Kritik geschätzt ...

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

2
Am Ende habe ich eine Variation davon verwendet. Vielen Dank für die Idee, Mitglieder zu zählen!
NateS

2
Seien Sie beim Ändern sehr vorsichtig Object.prototype- in den allermeisten Fällen wird davon abgeraten (Ergänzungen werden beispielsweise in allen for..in-Schleifen angezeigt). Vielleicht überlegen Object.equals = function(aObj, bObj) {...}?
Roy Tinker

3

Wenn Sie JSON-Objekte vergleichen, können Sie https://github.com/mirek/node-rus-diff verwenden

npm install rus-diff

Verwendungszweck:

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

Wenn zwei Objekte unterschiedlich sind, wird ein MongoDB-kompatibles {$rename:{...}, $unset:{...}, $set:{...}}Objekt zurückgegeben.


3

Ich stand vor dem gleichen Problem und beschloss, meine eigene Lösung zu schreiben. Da ich aber auch Arrays mit Objekten vergleichen möchte und umgekehrt, habe ich eine generische Lösung entwickelt. Ich habe beschlossen, die Funktionen zum Prototyp hinzuzufügen, aber man kann sie leicht in eigenständige Funktionen umschreiben. Hier ist der 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;
}

Dieser Algorithmus ist in zwei Teile unterteilt. Die Funktion equals selbst und eine Funktion zum Ermitteln des numerischen Index einer Eigenschaft in einem Array / Objekt. Die Suchfunktion wird nur benötigt, weil indexof nur Zahlen und Zeichenfolgen und keine Objekte findet.

Man kann es so nennen:

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

Die Funktion gibt entweder true oder false zurück, in diesem Fall true. Der Algorithmus ermöglicht auch den Vergleich zwischen sehr komplexen Objekten:

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

Das obere Beispiel gibt true zurück, obwohl die Eigenschaften eine andere Reihenfolge haben. Ein kleines Detail, auf das Sie achten sollten: Dieser Code sucht auch nach demselben Typ von zwei Variablen, sodass "3" nicht mit 3 identisch ist.


3

Ich sehe Spaghetti-Code-Antworten. Ohne die Verwendung von Bibliotheken von Drittanbietern ist dies sehr einfach.

Sortieren Sie zunächst die beiden Objekte nach Schlüssel und Schlüsselnamen.

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)

Verwenden Sie dann einfach eine Zeichenfolge, um sie zu vergleichen.

JSON.stringify(objectOne) === JSON.stringify(objectTwo)

Dies funktioniert auch nicht für Deep Copy, es hat nur eine Iterationstiefe.
Andras

Ich denke, @andras bedeutet, dass Sie Schlüssel von verschachtelten Objekten rekursiv sortieren müssen.
Davi Lima

2

Ich würde von Hashing oder Serialisierung abraten (wie die JSON-Lösung vorschlägt). Wenn Sie testen müssen, ob zwei Objekte gleich sind, müssen Sie definieren, was gleich bedeutet. Es kann sein, dass alle Datenelemente in beiden Objekten übereinstimmen, oder dass die Speicherorte übereinstimmen müssen (dh beide Variablen verweisen auf dasselbe Objekt im Speicher), oder dass nur ein Datenelement in jedem Objekt übereinstimmen muss.

Kürzlich habe ich ein Objekt entwickelt, dessen Konstruktor bei jeder Erstellung einer Instanz eine neue ID erstellt (beginnend mit 1 und inkrementierend um 1). Dieses Objekt verfügt über eine isEqual-Funktion, die diesen ID-Wert mit dem ID-Wert eines anderen Objekts vergleicht und true zurückgibt, wenn sie übereinstimmen.

In diesem Fall habe ich "gleich" so definiert, dass die ID-Werte übereinstimmen. Da jede Instanz eine eindeutige ID hat, kann dies verwendet werden, um die Idee durchzusetzen, dass übereinstimmende Objekte ebenfalls denselben Speicherort belegen. Obwohl das nicht nötig ist.


2

Es ist nützlich, zwei Objekte als gleich zu betrachten, wenn sie für alle Eigenschaften dieselben Werte und für alle verschachtelten Objekte und Arrays rekursiv haben. Ich betrachte auch die folgenden zwei Objekte als gleich:

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

Ebenso können Arrays "fehlende" Elemente und undefinierte Elemente enthalten. Ich würde diese auch gleich behandeln:

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

Eine Funktion, die diese Definition von Gleichheit implementiert:

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

Quellcode (einschließlich der Hilfsfunktionen generalType und uniqueArray): Unit Test und Test Runner hier .


2

Ich mache mit dieser Funktion folgende Annahmen:

  1. Sie steuern die Objekte, die Sie vergleichen, und Sie haben nur primitive Werte (dh keine verschachtelten Objekte, Funktionen usw.).
  2. Ihr Browser unterstützt Object.keys .

Dies sollte als Demonstration einer einfachen Strategie betrachtet werden.

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

2

Dies ist eine Ergänzung für alle oben genannten, kein Ersatz. Wenn Sie Objekte mit flachem Vergleich schnell vergleichen müssen, ohne zusätzliche rekursive Fälle überprüfen zu müssen. Hier ist ein Schuss.

Dies ist vergleichbar mit: 1) Gleichheit der Anzahl der eigenen Eigenschaften, 2) Gleichheit der Schlüsselnamen, 3) wenn bCompareValues ​​== true, Gleichheit der entsprechenden Eigenschaftswerte und ihrer Typen (dreifache Gleichheit)

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

2

Zum Vergleichen von Schlüsseln für Objektinstanzen mit einfachen Schlüssel / Wert-Paaren verwende ich:

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

Sobald die Schlüssel verglichen sind, reicht eine einfache zusätzliche for..inSchleife aus.

Die Komplexität ist O (N * N), wobei N die Anzahl der Schlüssel ist.

Ich hoffe / vermute, dass Objekte, die ich definiere, nicht mehr als 1000 Eigenschaften enthalten ...


2

Ich weiß, dass dies ein bisschen alt ist, aber ich möchte eine Lösung hinzufügen, die ich für dieses Problem gefunden habe. Ich hatte ein Objekt und wollte wissen, wann sich seine Daten geändert haben. "etwas ähnliches wie Object.observe" und was ich getan habe war:

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

Dies hier kann dupliziert werden und ein anderer Satz von Arrays erstellt werden, um die Werte und Schlüssel zu vergleichen. Dies ist sehr einfach, da sie jetzt Arrays sind und false zurückgeben, wenn Objekte unterschiedliche Größen haben.


Dies wird immer zurückgegeben false, da Arrays nicht nach Wert verglichen werden, z [1,2] != [1,2].
Fock
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.