The Why
My buddy and I have been working on a web app and he recently needed a way to compare and ensure that two JS native instances were of the same type and that they were strictly equal (===
vs a loose equality check, ==
). After you do the basic check to ensure that the two natives are of the same type, checking strict equality is a piece of cake for some types like Number
, String
, and Boolean
. The interesting checks come when dealing with the other types though. The code is worth a thousand posts in this case, so let’s cut to the chase:
The Code
If you are unfamiliar with things like typeOf
, Array.from
, and Object.extend
– (THIS DOES NOT EXTEND THE PROTOTYPE OF OBJECT), that’s OK because those are little helpers provided by the awesome MooTools JS framework which I contribute to 😉
Object.extend({ 'equals': function(first, second){ if (first !== second){ var type = typeOf(first), every = Array.every; if (type != typeOf(second)) return false; switch (type){ case 'string': case 'regexp': return String(first) == String(second); case 'date': return first.getTime() == second.getTime(); case 'arguments': first = Array.from(first); second = Array.from(second); case 'object': every = Object.every; case 'array': case 'object': case 'arguments': if (Object.getLength(first) != Object.getLength(second)) return false; return every(first, function(value, i){ return (i in second) && Object.equals(value, second[i]); }); } } else return true; } });
Step 1: Type checking
In this code block, I first start by type checking with MooTools’ typeOf
, this helper is a bit better than the native typeof
because it distinguishes between arrays, arguments objects, and DOM collections (also node lists), as well as a few other type-check oddities.
Step 2: Use the right equality check
Once I know both types are the same, we throw that into a switch to sift it into the right equality check case. You’ll notice there are very different methods employed to ascertain equality depending on the type you’re dealing with.
Step 3: Some checks are easier than others…
As stated above, numbers, strings, and booleans are all easy to check, so we knock them out in one case. Next up is Regexp
, if you just compare them strait-up, two regexp objects will always report false, even if their regexp matching characters are identical. The best way there is to use toString
on each to compare the actual matching characters. Next is Date. I chose not to use toString to compare date objects because the output is a low resolution time that only goes to minutes. To know two dates are exactly the same, instead I use getTime(), which gives me millisecond precision. Lastly are Array and Object. These two are not too much harder, they just require iterating the array or walking the object, then using the Object.equals method recursively on the values descending to the full depth of the instance.
What about Function
equality?
Due to the nature of the need, and the nearly impossible task of ensuring functional equality on unnamed and non-cached functions, this was not a concern for me and is not likely a concern for the broad range of use-cases.
Hope this helps, enjoy!
(If you see any errors or I missed a type you think should be included, let me know in the comments!)
The code defines the ‘object’ and ‘arguments’ case labels twice each.
The following line is also missing a closing parenthesis after the condition expression, before the return:
   if (Object.getLength(first) != Object.getLength(second) return false;
 The use of the “object” and “arguments” cases twice in the switch block is by design. The first references are needed to do a set of logic specific to “object” and “argument” inputs, the last compound case block operates on all object-like, non-array inputs.
Hey, I ended up noticing the case block fell through after I posted, during putting together my own version. I should have edited my comment. Thanks for following up.
Here’s a roughly equivalent version that I put together that works for Node.js or any other environment that supports ECMAScript 5 and doesn’t have any dependency upon MooTools or any other JS lib.
var deepEqual = function(first, second)
{
if (first === second)
return true;
var type = typeof(first),
firstKeys, secondKeys;
if (type != typeof(second))
return false;
switch (type) {
case 'string':
case 'regexp':
return String(first) == String(second);
case 'date':
return first.getTime() == second.getTime();
case 'object':
case 'array':
// Use ECMAScript 5 feature: Object.keys
firstKeys = Object.keys(first);
secondKeys = Object.keys(second);
if (firstKeys.length != secondKeys.length)
return false;
for (var i = 0; i < firstKeys.length; i++) {
var key = firstKeys[i];
// Fail if keys defined in different order
if (secondKeys[i] !== key)
return false;
// Fail if key's defined on one object and not on the other
if (typeof(second[key]) === 'undefined')
return false;
// Recurse through the object graph
if (!deepEqual(first[key], second[key]))
return false;
}
return true;
default:
throw Error('Unsupported type', type);
}
};