Frage Objektvergleich in JavaScript [duplizieren]


Diese Frage hat hier bereits eine Antwort:

Was ist der beste Weg, um Objekte in JavaScript zu vergleichen?

Beispiel:

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

ich weiß das Zwei Objekte sind gleich, wenn sie sich auf genau dasselbe Objekt beziehenAber gibt es eine Möglichkeit zu überprüfen, ob sie die gleichen Attribute haben?

Der folgende Weg funktioniert für mich, aber ist es die einzige Möglichkeit?

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

785
2017-07-01 12:18


Ursprung


Antworten:


Leider gibt es keinen perfekten Weg, außer du benutzt es _proto_ rekursiv und greifen Sie auf alle nicht aufzählbaren Eigenschaften zu, aber das funktioniert nur in Firefox.

Das Beste, was ich tun kann, ist, Nutzungsszenarien zu erraten.


1) Schnell und begrenzt.

Funktioniert, wenn Sie einfache JSON-style-Objekte ohne Methoden und DOM-Knoten darin haben:

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

Die ORDER der Eigenschaften ist WICHTIG, daher gibt diese Methode false für folgende Objekte zurück:

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

2) langsam und generischer.

Vergleicht Objekte, ohne in Prototypen zu graben, vergleicht dann rekursiv Projektionen von Eigenschaften und vergleicht auch Konstruktoren.

Das ist fast der richtige Algorithmus:

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

  function compare2Objects (x, y) {
    var p;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return true;
  }

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

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

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

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

  return true;
}

Bekannte Probleme (naja, sie haben eine sehr niedrige Priorität, wahrscheinlich werden Sie sie nie bemerken):

  • Objekte mit unterschiedlicher Prototypstruktur aber gleicher Projektion
  • Funktionen können den gleichen Text haben, beziehen sich jedoch auf verschiedene Schließungen

Tests: Passtests sind von Wie ermittelt man die Gleichheit für zwei JavaScript-Objekte?.


923
2017-07-17 16:08



Hier ist meine kommentierte Lösung in ES3 (blutige Details nach dem Code):

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

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

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

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

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

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

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

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

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

Bei der Entwicklung dieser Lösung habe ich mich besonders mit der Effizienz von Eckkästen befasst und dabei versucht, eine einfache Lösung zu finden, die hoffentlich mit einiger Eleganz funktioniert. JavaScript erlaubt beides Null und nicht definiert Eigenschaften und Objekte haben Prototypen Ketten das kann zu sehr unterschiedlichen Verhaltensweisen führen, wenn es nicht überprüft wird.

Zuerst habe ich beschlossen zu erweitern Objekt Anstatt von Objekt.prototyp, hauptsächlich weil Null könnte nicht zu den Objekten des Vergleichs gehören und das glaube ich Null sollte ein gültiges Objekt zum Vergleich sein. Englisch: www.germnews.de/archive/dn/1996/02/15.html Weitere legitime Bedenken werden von anderen bezüglich der Verlängerung der Objekt.prototyp bezüglich möglicher Nebenwirkungen auf den Code anderer.

Es ist besonders darauf zu achten, dass JavaScript die Einstellung von Objekteigenschaften erlaubt nicht definiert, d. h. es gibt Eigenschaften, auf die Werte gesetzt sind nicht definiert. Die obige Lösung überprüft, dass für beide Objekte dieselben Eigenschaften festgelegt sind nicht definiert Gleichheit melden. Dies kann nur erreicht werden, indem das Vorhandensein von Eigenschaften überprüft wird Object.hasOwnProperty (Eigenschaftsname). Beachten Sie auch das JSON.Stringify () Entfernt Eigenschaften, auf die festgelegt ist nicht definiertDaher ignorieren Vergleiche mit diesem Formular Eigenschaften, die auf den Wert festgelegt sind nicht definiert.

Funktionen sollten nur dann als gleich angesehen werden, wenn sie die gleiche Referenz haben, nicht nur den gleichen Code, denn dies würde den Prototyp dieser Funktionen nicht berücksichtigen. Der Vergleich der Code-Zeichenfolge funktioniert nicht, um sicherzustellen, dass sie das gleiche Prototyp-Objekt haben.

Die zwei Objekte sollten gleich sein Prototyp-Kette, nicht nur die gleichen Eigenschaften. Dies kann nur browserübergreifend durch Vergleichen der Konstrukteur beider Objekte für strikte Gleichheit. ECMAScript 5 würde erlauben, ihren tatsächlichen Prototyp zu testen Object.getPrototypeOf (). Einige Webbrowser bieten auch eine __proto__ Eigenschaft, die das Gleiche macht. Eine mögliche Verbesserung des obigen Codes würde es erlauben, eine dieser Methoden zu verwenden, wenn sie verfügbar ist.

Der Einsatz strenger Vergleiche ist hier von höchster Bedeutung 2 sollte nicht als gleichwertig betrachtet werden "2,0000", Noch falsch sollte als gleich betrachtet werden Null, nicht definiert, oder 0.

Effizienzbetrachtungen führen dazu, dass ich so schnell wie möglich auf Gleichheit der Eigenschaften vergleiche. Dann, nur wenn das fehlgeschlagen ist, suchen Sie nach dem Art der diese Eigenschaften. Der Geschwindigkeitsschub könnte bei großen Objekten mit vielen skalaren Eigenschaften signifikant sein.

Es sind nicht mehr zwei Schleifen erforderlich, die ersten, um Eigenschaften vom linken Objekt zu prüfen, die zweiten, um Eigenschaften von rechts zu prüfen und nur Existenz (nicht Wert) zu überprüfen, um diese Eigenschaften zu erfassen, die mit dem definiert sind nicht definiert Wert.

Insgesamt behandelt dieser Code die meisten Eckfälle in nur 16 Codezeilen (ohne Kommentare).

Update (13.08.2015). Ich habe eine bessere Version als die Funktion implementiert value_equals () das ist schneller, handhabt korrekt Eckfälle wie NaN und 0 anders als -0, und erzwingt optional das Korrigieren der Eigenschaften von Objekten und das Testen auf zyklische Referenzen, unterstützt durch mehr als 100 automatisierte Tests Im Rahmen des Toubkal Projekttest-Suite.


154
2017-07-15 22:21



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

Einfache Möglichkeit, ONE-LEVEL-Objekte zu vergleichen.


21
2018-05-02 15:27



Sicherlich nicht der einzige Weg - Sie könnten eine Methode entwickeln (gegen Objekt hier, aber ich würde sicherlich nicht vorschlagen, Object für Live-Code zu verwenden), um C # / Java-Stil-Vergleichsmethoden zu replizieren.

Bearbeiten, da ein allgemeines Beispiel zu erwarten ist:

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

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

    return true;
}

Beachten Sie, dass das Testen von Methoden mit toString () ist absolut nicht gut genug aber eine Methode, die akzeptabel wäre, ist wegen des Problems, dass Whitespace eine Bedeutung hat oder nicht, sehr schwer, geschweige denn Synonymverfahren und Methoden, die dasselbe Ergebnis mit verschiedenen Implementierungen erzeugen. Und die Probleme des Prototyping gegen Objekt im Allgemeinen.


19
2017-07-01 12:25



Der folgende Algorithmus behandelt selbstreferentielle Datenstrukturen, Zahlen, Strings, Daten und natürlich einfach verschachtelte Javascript-Objekte:

Objekte werden als gleichwertig betrachtet

  • Sie sind genau gleich pro === (String und Nummer werden zuerst ausgepackt, um sicherzustellen, dass 42 ist äquivalent zu Number(42))
  • oder sie sind beide Daten und haben das gleiche valueOf()
  • oder sie sind beide vom selben Typ und nicht null und ...
    • sie sind keine Objekte und sind gleich pro == (fängt Zahlen / Strings / Boolean)
    • oder, Eigenschaften mit ignorierend undefined Wert haben sie die gleichen Eigenschaften, die alle als rekursiv gleichwertig betrachtet werden.

Funktionen werden vom Funktionstext nicht als identisch angesehen. Dieser Test ist nicht ausreichend, da Funktionen unterschiedliche Schließungen haben können. Funktionen werden nur dann als gleich betrachtet, wenn === sagt so (aber Sie könnten diese äquivalente Beziehung leicht erweitern, sollten Sie sich dazu entscheiden).

Unendliche Schleifen, die möglicherweise durch kreisförmige Datenstrukturen verursacht werden, werden vermieden. Wann areEquivalent versucht, die Gleichheit zu widerlegen und rekrutiert in die Eigenschaften eines Objekts, um dies zu tun, verfolgt es die Objekte, für die dieser Untervergleich benötigt wird. Wenn Gleichheit widerlegt werden kann, unterscheiden sich einige erreichbare Eigenschaftenpfade zwischen den Objekten, und dann muss es einen kürzesten so erreichbaren Pfad geben, und der kürzeste erreichbare Pfad kann keine Zyklen enthalten, die in beiden Pfaden vorhanden sind; es ist OK, die Gleichheit anzunehmen, wenn Objekte rekursiv verglichen werden. Die Annahme wird in einer Eigenschaft gespeichert areEquivalent_Eq_91_2_34, die nach der Verwendung gelöscht wird, aber wenn das Objektdiagramm bereits eine solche Eigenschaft enthält, ist das Verhalten nicht definiert. Die Verwendung einer solchen Marker-Eigenschaft ist notwendig, weil JavaScript keine Wörterbücher unterstützt, die beliebige Objekte als Schlüssel verwenden.

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

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

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

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

15
2018-06-19 14:29



Ich habe diesen Code zum Objektvergleich geschrieben und es scheint zu funktionieren. Überprüfen Sie die Behauptungen:


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

function objectEquals(v1, v2) {

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

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

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

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

assert.isTrue(objectEquals("hi","hi"));
assert.isTrue(objectEquals(5,5));
assert.isFalse(objectEquals(5,10));

assert.isTrue(objectEquals([],[]));
assert.isTrue(objectEquals([1,2],[1,2]));
assert.isFalse(objectEquals([1,2],[2,1]));
assert.isFalse(objectEquals([1,2],[1,2,3]));

assert.isTrue(objectEquals({},{}));
assert.isTrue(objectEquals({a:1,b:2},{a:1,b:2}));
assert.isTrue(objectEquals({a:1,b:2},{b:2,a:1}));
assert.isFalse(objectEquals({a:1,b:2},{a:1,b:3}));

assert.isTrue(objectEquals({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}},{1:{name:"mhc",age:28}, 2:{name:"arb",age:26}}));
assert.isFalse(objectEquals({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}},{1:{name:"mhc",age:28}, 2:{name:"arb",age:27}}));

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

11
2017-10-03 11:01



Ich habe den obigen Code etwas modifiziert. für mich 0! == falsch und null! == undefiniert. Wenn du keine so strenge Kontrolle brauchst, entferne eins "=" anmelden "das [p]! == x [p]"Innerhalb des Codes.

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

Dann habe ich es mit den nächsten Objekten getestet:

var a = {a: 'text', b:[0,1]};
var b = {a: 'text', b:[0,1]};
var c = {a: 'text', b: 0};
var d = {a: 'text', b: false};
var e = {a: 'text', b:[1,0]};
var f = {a: 'text', b:[1,0], f: function(){ this.f = this.b; }};
var g = {a: 'text', b:[1,0], f: function(){ this.f = this.b; }};
var h = {a: 'text', b:[1,0], f: function(){ this.a = this.b; }};
var i = {
    a: 'text',
    c: {
        b: [1, 0],
        f: function(){
            this.a = this.b;
        }
    }
};
var j = {
    a: 'text',
    c: {
        b: [1, 0],
        f: function(){
            this.a = this.b;
        }
    }
};
var k = {a: 'text', b: null};
var l = {a: 'text', b: undefined};

a == b erwartet wahr; kehrte wahr zurück

a == c erwartet falsch; falsch zurückgegeben

c == d erwartet falsch; falsch zurückgegeben

a == e erwartet falsch; falsch zurückgegeben

f == g erwartet wahr; kehrte wahr zurück

h == g erwartet falsch; falsch zurückgegeben

i == j erwartet wahr; kehrte wahr zurück

d == k erwartet falsch; falsch zurückgegeben

k == l erwartete falsch; falsch zurückgegeben


5
2018-04-29 09:17