Objectvergelijking in JavaScript

Wat is de beste manier om objecten in JavaScript te vergelijken?

Voorbeeld:

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

Ik weet dat twee objecten gelijk zijn als ze naar exact hetzelfde object verwijzen, maar is er een manier om te controleren of ze dezelfde attributenwaarden hebben?

De volgende manier werkt voor mij, maar is dit de enige mogelijkheid?

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

Antwoord 1, autoriteit 100%

Helaas is er geen perfecte manier, tenzij je _proto_recursief gebruikt en toegang krijgt tot alle niet-opsombare eigenschappen, maar dit werkt alleen in Firefox.

Dus ik kan het beste gebruiksscenario’s raden.


1) Snel en beperkt.

Werkt als je eenvoudige JSON-achtige objecten hebt zonder methoden en DOM-knooppunten erin:

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

De ORDER van de eigenschappen IS BELANGRIJK, dus deze methode retourneert false voor de volgende objecten:

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

2) Langzaam en algemener.

Vergelijkt objecten zonder in prototypes te graven, vergelijkt vervolgens de projecties van eigenschappen recursief en vergelijkt ook constructeurs.

Dit is bijna het juiste algoritme:

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

Bekende problemen (nou ja, ze hebben een zeer lage prioriteit, u zult ze waarschijnlijk nooit opmerken):

  • objecten met een andere prototypestructuur maar dezelfde projectie
  • functies kunnen identieke tekst hebben, maar verwijzen naar verschillende afsluitingen

Testen:geslaagde tests zijn van Hoe bepaal je de gelijkheid voor twee JavaScript-objecten?.


Antwoord 2, autoriteit 14%

Hier is mijn ES3becommentarieerde oplossing (bloederige details na de 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;
}

Bij het ontwikkelen van deze oplossing heb ik in het bijzonder gekeken naar hoeksituaties, efficiëntie, en toch geprobeerd een eenvoudige oplossing op te leveren die werkt, hopelijk met wat elegantie. JavaScript staat zowel nullals undefinedeigenschappen toe en objecten hebben prototypeketensdat kan leiden tot heel ander gedrag als het niet wordt aangevinkt.

Eerst heb ik ervoor gekozen om Object.prototypeniet uit te breiden, vooral omdat nullniet een van de de objecten van de vergelijking en dat ik geloof dat nulleen geldig object moet zijn om met een ander te vergelijken. Er zijn ook andere legitieme zorgen die door anderen zijn opgemerkt met betrekking tot de extensie van Object.prototypemet betrekking tot mogelijke bijwerkingen op de code van anderen.

Er moet speciale aandacht worden besteed aan de mogelijkheid dat JavaScript toestaat dat objecteigenschappen kunnen worden ingesteld op undefined, dat wil zeggen dat er eigenschappen zijn waarvan de waarden zijn ingesteld op niet gedefinieerd. De bovenstaande oplossing verifieert dat beide objecten dezelfde eigenschappen hebben die zijn ingesteld op undefinedom gelijkheid te melden. Dit kan alleen worden bereikt door het bestaan van eigenschappen te controleren met Object.hasOwnProperty( property_name ). Merk ook op dat JSON.stringify()eigenschappen verwijdert die zijn ingesteld op undefined, en dat daarom vergelijkingen die dit gebruiken form negeert eigenschappen die zijn ingesteld op de waarde undefined.

Functies moeten alleen als gelijk worden beschouwd als ze dezelfde referentie delen, niet alleen dezelfde code, omdat dit geen rekening houdt met het prototype van deze functies. Dus het vergelijken van de codereeks werkt niet om te garanderen dat ze hetzelfde prototype-object hebben.

De twee objecten moeten dezelfde prototypeketenhebben, niet alleen dezelfde eigenschappen. Dit kan alleen cross-browser worden getest door de constructorvan beide objecten te vergelijken voor strikte gelijkheid. ECMAScript 5 zou het mogelijk maken om hun daadwerkelijke prototype te testen met behulp van Object.getPrototypeOf(). Sommige webbrowsers bieden ook een eigenschap __proto__die hetzelfde doet. Een mogelijke verbetering van de bovenstaande code zou het mogelijk maken om een van deze methoden te gebruiken wanneer beschikbaar.

Het gebruik van strikte vergelijkingen is hier van het grootste belang, omdat 2niet moet worden beschouwd als gelijk aan “2.0000”, noch falsemag worden beschouwd als gelijk aan null, undefined, of 0.

Efficiëntie-overwegingen brengen me ertoe om zo snel mogelijk te vergelijken voor gelijkheid van eigenschappen. Zoek dan, alleen als dat niet lukt, naar het type vandeze eigenschappen. De snelheidsboost kan aanzienlijk zijn bij grote objecten met veel scalaire eigenschappen.

Er zijn niet meer twee lussen nodig, de eerste om eigenschappen van het linkerobject te controleren, de tweede om eigenschappen van het rechterobject te controleren en alleen het bestaan (geen waarde) te verifiëren, om deze eigenschappen op te vangen die zijn gedefinieerd met de undefinedwaarde.

Over het algemeen verwerkt deze code de meeste hoekgevallen in slechts 16 regels code (zonder commentaar).


Update (13-8-2015). Ik heb een betere versie geïmplementeerd, zoals de functie value_equals()die sneller is, goed omgaat met hoekgevallen zoals NaN en 0 anders dan -0, optioneel de eigenschappenvolgorde van objecten afdwingt en testen op cyclische verwijzingen, ondersteund door meer dan 100 geautomatiseerde testsals onderdeel van de Toubkal-projecttestsuite.


Antwoord 3, autoriteit 3%

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

Eenvoudige manier om objecten met alleen ONE-LEVEL te vergelijken.


Antwoord 4, autoriteit 2%

Zeker niet de enige manier – je zou een prototype kunnen maken van een methode (hier tegen Object, maar ik zou zeker niet aanraden Object voor live code te gebruiken) om vergelijkingsmethoden in C#/Java-stijl te repliceren.

Bewerken, aangezien een algemeen voorbeeld lijkt te worden verwacht:

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

Merk op dat het testen van methoden met toString() absoluut niet goed genoegis, maar een methode die acceptabel zou zijn, is erg moeilijk vanwege het probleem of witruimte betekenis heeft of niet, laat staan synoniem methoden en methoden het produceren van hetzelfde resultaat met verschillende implementaties. Ende problemen van prototyping tegen Object in het algemeen.


Antwoord 5

Het volgende algoritme zal omgaan met naar zichzelf verwijzende datastructuren, getallen, strings, datums en natuurlijk gewoon geneste Javascript-objecten:

Objecten worden als gelijkwaardig beschouwd wanneer

  • Ze zijn exact gelijk per ===(String en Number worden eerst uitgepakt om ervoor te zorgen dat 42gelijk is aan Number(42))
  • of het zijn beide datums en hebben dezelfde valueOf()
  • of ze zijn allebei van hetzelfde type en niet null en…
    • het zijn geen objecten en zijn gelijk per ==(vangt getallen/strings/booleans op)
    • of, door eigenschappen met een undefinedwaarde te negeren, hebben ze dezelfde eigenschappen die allemaal als recursief equivalent worden beschouwd.

Functiesworden niet als identiek beschouwd door functietekst. Deze test is onvoldoende omdat functies verschillende sluitingen kunnen hebben. Functies worden alleen als gelijk beschouwd als ===dat zegt (maar je zou die equivalente relatie gemakkelijk kunnen uitbreiden als je daarvoor kiest).

Oneindige lussen, mogelijk veroorzaakt door circulaire datastructuren, worden vermeden. Wanneer areEquivalentgelijkheid probeert te weerleggen en hiervoor terugkeert naar de eigenschappen van een object, worden de objecten bijgehouden waarvoor deze subvergelijking nodig is. Als gelijkheid kan worden weerlegd, dan verschilt een bereikbaar eigenschapspad tussen de objecten, en dan moet er een kortst zo’n bereikbaar pad zijn, en dat kortste bereikbare pad kan geen cycli bevatten die in beide paden aanwezig zijn; d.w.z. het is OK om gelijkheid aan te nemen bij het recursief vergelijken van objecten. De aanname wordt opgeslagen in een eigenschap areEquivalent_Eq_91_2_34, die na gebruik wordt verwijderd, maar als de objectgrafiek al een dergelijke eigenschap bevat, is het gedrag niet gedefinieerd. Het gebruik van een dergelijke markereigenschap is noodzakelijk omdat javascript geen woordenboeken ondersteunt die willekeurige objecten als sleutels gebruiken.

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

Antwoord 6

Ik heb dit stukje code geschreven om objecten te vergelijken en het lijkt te werken. controleer de beweringen:


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

Antwoord 7

Ik heb de bovenstaande code een beetje aangepast. voor mij 0 !== falseen null !== undefined. Als je zo’n strikte controle niet nodig hebt, verwijder dan een “=” teken in “this[p] !== x[p]” in de code.

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

Vervolgens heb ik het getest met de volgende objecten:

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 verwacht waar; geretourneerd waar

a==c verwacht onwaar; onwaar geretourneerd

c==d verwacht onwaar; onwaar geretourneerd

a==e verwacht onwaar; onwaar geretourneerd

f==g verwacht waar; geretourneerd waar

h==g verwacht onwaar; onwaar geretourneerd

i==j verwacht waar; geretourneerd waar

d==k verwacht onwaar; onwaar geretourneerd

k==l verwacht vals; onwaar geretourneerd


Antwoord 8

Hier is mijn versie, vrijwel alles uit deze thread is geïntegreerd (hetzelfde geldt voor de testgevallen):

Object.defineProperty(Object.prototype, "equals", {
    enumerable: false,
    value: function (obj) {
        var p;
        if (this === obj) {
            return true;
        }
        // some checks for native types first
        // function and sring
        if (typeof(this) === "function" || typeof(this) === "string" || this instanceof String) { 
            return this.toString() === obj.toString();
        }
        // number
        if (this instanceof Number || typeof(this) === "number") {
            if (obj instanceof Number || typeof(obj) === "number") {
                return this.valueOf() === obj.valueOf();
            }
            return false;
        }
        // null.equals(null) and undefined.equals(undefined) do not inherit from the 
        // Object.prototype so we can return false when they are passed as obj
        if (typeof(this) !== typeof(obj) || obj === null || typeof(obj) === "undefined") {
            return false;
        }
        function sort (o) {
            var result = {};
            if (typeof o !== "object") {
                return o;
            }
            Object.keys(o).sort().forEach(function (key) {
                result[key] = sort(o[key]);
            });
            return result;
        }
        if (typeof(this) === "object") {
            if (Array.isArray(this)) { // check on arrays
                return JSON.stringify(this) === JSON.stringify(obj);                
            } else { // anyway objects
                for (p in this) {
                    if (typeof(this[p]) !== typeof(obj[p])) {
                        return false;
                    }
                    if ((this[p] === null) !== (obj[p] === null)) {
                        return false;
                    }
                    switch (typeof(this[p])) {
                    case 'undefined':
                        if (typeof(obj[p]) !== 'undefined') {
                            return false;
                        }
                        break;
                    case 'object':
                        if (this[p] !== null 
                                && obj[p] !== null 
                                && (this[p].constructor.toString() !== obj[p].constructor.toString() 
                                        || !this[p].equals(obj[p]))) {
                            return false;
                        }
                        break;
                    case 'function':
                        if (this[p].toString() !== obj[p].toString()) {
                            return false;
                        }
                        break;
                    default:
                        if (this[p] !== obj[p]) {
                            return false;
                        }
                    }
                };
            }
        }
        // at least check them with JSON
        return JSON.stringify(sort(this)) === JSON.stringify(sort(obj));
    }
});

Hier is mijn TestCase:

   assertFalse({}.equals(null));
    assertFalse({}.equals(undefined));
    assertTrue("String", "hi".equals("hi"));
    assertTrue("Number", new Number(5).equals(5));
    assertFalse("Number", new Number(5).equals(10));
    assertFalse("Number+String", 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}}));
    assertTrue("Function", (function(x){return x;}).equals(function(x){return x;}));
    assertFalse("Function", (function(x){return x;}).equals(function(y){return y+2;}));
    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};
    assertTrue(a.equals(b));
    assertFalse(a.equals(c));
    assertFalse(c.equals(d));
    assertFalse(a.equals(e));
    assertTrue(f.equals(g));
    assertFalse(h.equals(g));
    assertTrue(i.equals(j));
    assertFalse(d.equals(k));
    assertFalse(k.equals(l));

Antwoord 9

Als u expliciet naar methoden wilt zoeken, kunt u de methoden method.toSource() of method.toString() gebruiken.


Antwoord 10

Als je zonder de JSON-bibliotheek werkt, kan dit je misschien helpen:

Object.prototype.equals = function(b) {
    var a = this;
    for(i in a) {
        if(typeof b[i] == 'undefined') {
            return false;
        }
        if(typeof b[i] == 'object') {
            if(!b[i].equals(a[i])) {
                return false;
            }
        }
        if(b[i] != a[i]) {
            return false;
        }
    }
    for(i in b) {
        if(typeof a[i] == 'undefined') {
            return false;
        }
        if(typeof a[i] == 'object') {
            if(!a[i].equals(b[i])) {
                return false;
            }
        }
        if(a[i] != b[i]) {
            return false;
        }
    }
    return true;
}
var a = {foo:'bar', bar: {blub:'bla'}};
var b = {foo:'bar', bar: {blub:'blob'}};
alert(a.equals(b)); // alert's a false

Other episodes