testing for (deep) equality is quite a difficult thing to get right. head over to github.com/loveencounterflow/jseq to see a test suite of popular equal() implementations that already covers many edge cases. the discussion in the docs is also quite thorough.
– flowJun 5 '14 at 11:43

The check against undefined will fail for when a property is defined but set to the undefined value. Use the in operator instead of typeof to avoid this: p in x. Also comparing functions by string value is highly unreliable. Apart from the usual reasons that function decomposition fails, it's also very common to have two functions with the same code but very different behaviour due to closures. eg. any function created by jQuery's $.proxy or Prototype's Function#bind. I'd just stick with comparing function identity.
– bobinceSep 24 '10 at 23:22

You shouldn't extend Object.prototype if it can be avoided. It causes ugly problems such as breaking for(var key in someObject) if there's no if(!someObject.hasOwnProperty(key)) continue; inside that loop.
– ThiefMasterJun 2 '11 at 21:44

12

the function comparison is wrong: functions may have identical text but refer to different closures. Better to just return this[p] === x[p].
– Eamon NerbonneJun 19 '11 at 12:37

18

Regarding 1)"The ORDER of the properties IS IMPORTANT, so this method will return false for following objects:" That's not necessarily true. That method may return false for those object, or it may not. There's no guarantee either way. That's why we don't use JSON.stringify comparison for Object comparison. There's no guarantees of order.
– user2437417Jun 30 '13 at 11:59

In developing this solution, I took a particular look at corner cases, efficiency, yet trying to yield a simple solution that works, hopefully with some elegance. JavaScript allows both null and undefined properties and objects have prototypes chains that can lead to very different behaviors if not checked.

First I have chosen to extend Object instead of Object.prototype, mostly because null could not be one of the objects of the comparison and that I believe that null should be a valid object to compare with another. There are also other legitimate concerns noted by others regarding the extension of Object.prototype regarding possible side effects on other's code.

Special care must taken to deal the possibility that JavaScript allows object properties can be set to undefined, i.e. there exists properties which values are set to undefined. The above solution verifies that both objects have the same properties set to undefined to report equality. This can only be accomplished by checking the existence of properties using Object.hasOwnProperty( property_name ). Also note that JSON.stringify() removes properties that are set to undefined, and that therefore comparisons using this form will ignore properties set to the value undefined.

Functions should be considered equal only if they share the same reference, not just the same code, because this would not take into account these functions prototype. So comparing the code string does not work to guaranty that they have the same prototype object.

The two objects should have the same prototype chain, not just the same properties. This can only be tested cross-browser by comparing the constructor of both objects for strict equality. ECMAScript 5 would allow to test their actual prototype using Object.getPrototypeOf(). Some web browsers also offer a __proto__ property that does the same thing. A possible improvement of the above code would allow to use one of these methods whenever available.

The use of strict comparisons is paramount here because 2 should not be considered equal to "2.0000", nor false should be considered equal to null, undefined, or 0.

Efficiency considerations lead me to compare for equality of properties as soon as possible. Then, only if that failed, look for the typeof these properties. The speed boost could be significant on large objects with lots of scalar properties.

No more that two loops are required, the first to check properties from the left object, the second to check properties from the right and verify only existence (not value), to catch these properties which are defined with the undefined value.

Overall this code handles most corner cases in only 16 lines of code (without comments).

Update (8/13/2015). I have implemented a better version, as the function value_equals() that is faster, handles properly corner cases such as NaN and 0 different than -0, optionally enforcing objects' properties order and testing for cyclic references, backed by more than 100 automated tests as part of the Toubkal project test suite.

I'm using this function in my code. Just found out that it runs out of stack when testing cyclical objects. I didn't implement it because I don't need it as of now, but wanted to let you know. jsfiddle.net/mendesjuan/uKtEy
– Juan MendesJun 21 '12 at 18:14

1

Actually, I found a method to detect if an object is cyclical. Added it to another fiddle, jsfiddle.net/mendesjuan/uKtEy/1 Now the function throws an error if an object is cyclical, which is a shame because your original version did work for cyclical objects, as long as the two object were pointing to the same object jsfiddle.net/mendesjuan/uKtEy/2
– Juan MendesJun 21 '12 at 18:24

2

@JuanMendes, if you need a version that tests cyclical objects, a much more efficient way would be to merge the cyclical test into Object:equals(), in order to test only one property right before the recursion: updated fiddle here jsfiddle.net/uKtEy/3
– Jean VincentJul 9 '12 at 19:29

1

why would you want to check the prototype chains? If you actually care about identity and not visible behaviour, just use ===. If you care about visible, readable properties, then the prototype chain doesn't matter.
– Eamon NerbonneJun 27 '13 at 14:08

Note that testing methods with toString() is absolutely not good enough but a method which would be acceptable is very hard because of the problem of whitespace having meaning or not, never mind synonym methods and methods producing the same result with different implementations. And the problems of prototyping against Object in general.

thx, and same question as above...does this work with object methods?
– spankmaster79Jul 1 '09 at 12:51

Yes and no. It doesn't as written because the two methods will be different object instances (where not prototyped), but you could adapt this to a determine the methods are equivalent (though that's not straightforward).
– annakataJul 1 '09 at 13:08

3

You're only looping through "this" elements. Wouldn't your equals method return true when "this" is a subset of a larger "x"? Shouldn't you also add a for (p in x) loop?
– NosrednaJul 1 '09 at 15:48

2

The code is buggy. o1.equals(o2) returns true if all properties of o1 are equal to those of o2. But if o2 also has other properties that o1 does not have, the objects may be incorrectly marked as equal.
– molfJul 1 '09 at 15:49

Additionally, it would benefit from recursive comparison, using equals() internally to compare properties that are objects.
– molfJul 1 '09 at 15:50

The following algorithm will deal with self-referential data structures, numbers, strings, dates, and of course plain nested javascript objects:

Objects are considered equivalent when

They are exactly equal per === (String and Number are unwrapped first to ensure 42 is equivalent to Number(42))

or they are both dates and have the same valueOf()

or they are both of the same type and not null and...

they are not objects and are equal per == (catches numbers/strings/booleans)

or, ignoring properties with undefined value they have the same properties all of which are considered recursively equivalent.

Functions are not considered identical by function text. This test is insufficient because functions may have differing closures. Functions are only considered equal if === says so (but you could easily extend that equivalent relation should you choose to do so).

Infinite loops, potentially caused by circular datastructures, are avoided. When areEquivalent attempts to disprove equality and recurses into an object's properties to do so, it keeps track of the objects for which this sub-comparison is needed. If equality can be disproved, then some reachable property path differs between the objects, and then there must be a shortest such reachable path, and that shortest reachable path cannot contain cycles present in both paths; i.e. it is OK to assume equality when recursively comparing objects. The assumption is stored in a property areEquivalent_Eq_91_2_34, which is deleted after use, but if the object graph already contains such a property, behavior is undefined. The use of such a marker property is necessary because javascript doesn't support dictionaries using arbitrary objects as keys.

+1 for testing the constructor or sub objects. But why not testing that of the main object? And why not testing functions by reference instead of comparing strings using toString(), this is slow and unaccurate.
– Jean VincentJul 15 '11 at 8:56

really not good enough for the reasons I described
– annakataJul 1 '09 at 16:31

So you'd spin through the elements of the object, and check what the types are, then use toSource() or toString() when you find a function?
– NosrednaJul 1 '09 at 19:38

Nosredna, yes. That would give you the actual text of the function. annakata, I don't understand what's not good enough and what you are actually trying to do. Could you elaborate a bit?
– Nicolas RJul 2 '09 at 15:54

1

@snz3 - there's a serious problem with whitespace, dropped semi-colons and braces and similar syntax differences which may or may not have an impact, and are hard to determine without parsing i.e. decoupling from a raw string format. There's also the problem of fluctuating state and prototyping. Basically strings are not good enough at capturing the state of two objects.
– annakataJul 20 '09 at 21:55

JSON.stringify() removes properties that are set to undefined, and that therefore comparisons using this form will ignore properties set to the value undefined: assertFalse([1,2,null].equals([1,2,undefined])).
– Jean VincentJul 9 '12 at 20:20

You are stringifying arrays, but array can have complex objects inside
– DanJun 26 '13 at 7:37

Shouldn't this test assert false instead of true because one is an instance of Object and the other is a primative? assertTrue("Number", new Number(5).equals(5));
– Pete AlvinMay 6 '15 at 19:51