1502 Stimmen

Objektvergleich in JavaScript

Wie kann man Objekte in JavaScript am besten vergleichen?

Beispiel:

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

Ich weiß, dass zwei Objekte sind gleich, wenn sie sich auf genau dasselbe Objekt beziehen aber gibt es eine Möglichkeit zu überprüfen, ob sie dieselben Attributwerte haben?

Der folgende Weg funktioniert bei mir, aber ist das die einzige Möglichkeit?

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

1799voto

crazyx Punkte 16468

Leider gibt es keinen perfekten Weg, es sei denn, Sie verwenden _proto_ rekursiv auf alle nicht aufzählbaren Eigenschaften zugreifen, aber das funktioniert nur in Firefox.

Ich kann also bestenfalls Nutzungsszenarien erraten.


1) Schnell und begrenzt.

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

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

Die ORDNUNG der Eigenschaften ist wichtig, so dass diese Methode für die folgenden Objekte false zurückgibt:

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

2) Langsam und allgemeiner.

Vergleicht Objekte, ohne sich mit Prototypen zu befassen, vergleicht dann rekursiv die Projektionen von Eigenschaften und vergleicht auch Konstruktoren.

Dies ist ein fast korrekter 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 (nun, 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, sich aber auf unterschiedliche Abschlüsse beziehen

Tests: besteht Tests sind von Wie bestimmt man die Gleichheit zweier JavaScript-Objekte? .

229voto

Jean Vincent Punkte 11155

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

function object_equals( 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 besonders auf Eckfälle und Effizienz geachtet und versucht, eine einfache Lösung zu finden, die hoffentlich mit einer gewissen Eleganz funktioniert. JavaScript erlaubt sowohl null y undefiniert Eigenschaften und Objekte haben Prototypenketten die zu sehr unterschiedlichen Verhaltensweisen führen können, wenn sie nicht kontrolliert werden.

Erstens habe ich mich dafür entschieden, nicht zu verlängern Objekt.Prototyp vor allem, weil null nicht eines der Objekte des Vergleichs sein kann und dass ich glaube, dass null sollte ein gültiges Objekt zum Vergleich mit einem anderen sein. Es gibt auch andere berechtigte Bedenken, die von anderen hinsichtlich der Ausweitung der Objekt.Prototyp über mögliche Nebenwirkungen auf den Code anderer.

Besondere Aufmerksamkeit muss der Möglichkeit gewidmet werden, dass JavaScript erlaubt, Objekteigenschaften auf undefiniert , d.h. es gibt Eigenschaften, deren Werte auf undefiniert . Die obige Lösung stellt sicher, dass beide Objekte die gleichen Eigenschaften haben, die auf undefiniert Gleichheit zu melden. Dies kann nur erreicht werden, indem das Vorhandensein von Eigenschaften mit Object.hasOwnProperty( property_name ) . Beachten Sie auch, dass JSON.stringify() entfernt die Eigenschaften, die auf undefiniert und dass daher Vergleiche, die dieses Formular verwenden, Eigenschaften ignorieren, die auf den Wert undefiniert .

Funktionen sollten nur dann als gleichwertig betrachtet werden, wenn sie denselben Verweis haben, nicht nur denselben Code, da dies den Prototyp dieser Funktionen nicht berücksichtigen würde. Der Vergleich der Code-Zeichenkette garantiert also nicht, dass sie das gleiche Prototyp-Objekt haben.

Die beiden Objekte sollten die gleiche Prototypenkette und nicht nur die gleichen Eigenschaften. Dies kann nur browserübergreifend getestet werden, indem man die Konstrukteur der beiden Objekte für strikte Gleichheit. ECMAScript 5 würde es ermöglichen, ihren aktuellen Prototyp zu testen, indem man Object.getPrototypeOf() . Einige Webbrowser bieten auch eine __proto__ Eigenschaft, die das Gleiche bewirkt. Eine mögliche Verbesserung des obigen Codes würde es ermöglichen, eine dieser Methoden zu verwenden, wann immer sie verfügbar ist.

Die Verwendung strenger Vergleiche ist hier von entscheidender Bedeutung, denn 2 sollte nicht gleichgesetzt werden mit "2.0000" noch falsch sollte gleichgesetzt werden mit null , undefiniert , oder 0 .

Aus Gründen der Effizienz möchte ich so schnell wie möglich die Gleichheit der Eigenschaften vergleichen. Erst wenn dies nicht gelingt, suche ich nach der typeof diese Eigenschaften. Der Geschwindigkeitszuwachs könnte bei großen Objekten mit vielen skalaren Eigenschaften erheblich sein.

Es sind nicht mehr als zwei Schleifen erforderlich, die erste, um die Eigenschaften des linken Objekts zu prüfen, die zweite, um die Eigenschaften des rechten Objekts zu prüfen und nur das Vorhandensein (nicht den Wert) zu überprüfen, um diese Eigenschaften zu erfassen, die mit der Option undefiniert Wert.

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


Update (13.8.2015) . Ich habe eine bessere Version implementiert, da die Funktion wert_equals() das schneller ist, Eckfälle wie NaN und 0, die sich von -0 unterscheiden, richtig behandelt, optional die Reihenfolge der Eigenschaften von Objekten erzwingt und auf zyklische Referenzen prüft, unterstützt durch mehr als 100 automatisierte Tests als Teil der Toubkal Projekt-Testsuite.

63voto

pincopallo Punkte 583
  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 zum Vergleich von Objekten, die nur auf einer Ebene liegen.

22voto

annakata Punkte 72408

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

Bearbeiten, da ein allgemeines Beispiel erwartet zu werden scheint:

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() absolut nicht gut genug aber eine Methode, die akzeptabel wäre, ist sehr schwierig wegen des Problems, ob Leerzeichen eine Bedeutung haben oder nicht, ganz zu schweigen von synonymen Methoden und Methoden, die mit unterschiedlichen Implementierungen das gleiche Ergebnis liefern. Und die Probleme des Prototyping gegenüber Object im Allgemeinen.

21voto

Eamon Nerbonne Punkte 45041

Der folgende Algorithmus befasst sich mit selbstreferenziellen Datenstrukturen, Zahlen, Zeichenketten, Datumsangaben und natürlich mit einfachen verschachtelten Javascript-Objekten:

Objekte werden als gleichwertig betrachtet, wenn

  • Sie sind genau gleich pro === (String und Number werden zuerst ausgepackt, um sicherzustellen, dass 42 ist gleichbedeutend mit Number(42) )
  • oder sie sind beide Daten und haben die gleiche valueOf()
  • oder sie sind beide vom gleichen Typ und nicht null und...
    • sie sind keine Objekte und sind gleichwertig pro == (fängt Zahlen/Strings/Booleans)
    • oder, ohne Berücksichtigung von Eigenschaften mit undefined Wert haben sie dieselben Eigenschaften, die alle als rekursiv gleichwertig angesehen werden.

Funktionen werden im Funktionstext nicht als identisch angesehen. Dieser Test ist unzureichend, da Funktionen unterschiedliche Abschlüsse haben können. Funktionen werden nur dann als gleich angesehen, wenn === sagt dies (aber Sie könnten diese äquivalente Beziehung leicht erweitern, wenn Sie dies wünschen).

Unendliche Schleifen die durch zirkuläre Datenstrukturen verursacht werden können, werden vermieden. Wenn areEquivalent versucht, die Gleichheit zu widerlegen, und zu diesem Zweck auf die Eigenschaften eines Objekts zurückgreift, behält es die Objekte im Auge, für die dieser Teilvergleich erforderlich ist. Wenn die Gleichheit widerlegt werden kann, dann gibt es irgendeinen erreichbaren Eigenschaftspfad zwischen den Objekten, und dann muss es einen kürzesten solchen erreichbaren Pfad geben, und dieser kürzeste erreichbare Pfad darf keine Zyklen enthalten, die in beiden Pfaden vorhanden sind; d.h. es ist OK, Gleichheit anzunehmen, wenn man Objekte rekursiv vergleicht. Die Annahme wird in einer Eigenschaft gespeichert areEquivalent_Eq_91_2_34 die nach der Verwendung gelöscht wird, aber wenn der Objektgraph bereits eine solche Eigenschaft enthält, ist das Verhalten undefiniert. Die Verwendung einer solchen Markierungseigenschaft ist notwendig, weil Javascript keine Wörterbücher mit beliebigen Objekten als Schlüssel unterstützt.

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X