I have a deep equality comparison method that returns true
when two objects are equal (see method Doc):
_.isEqual = function (obj1, obj2) {
// Quick compare objects that don't have nested objects
if (_.type(obj1) === _.type(obj2) && !_.isPlainObject(obj1) && !_.isArray(obj1)) {
switch (_.type(obj1)) {
case "function" :
if (obj1.toString() !== obj2.toString()) return false;
break;
case "nan" :
if (obj1 === obj2) return false;
break;
default:
if (obj1 !== obj2) return false;
}
} else {
// When target or comparison is falsy we compare them directly
if (_.isFalsy(obj1) || _.isFalsy(obj2)) {
if (obj1 !== obj2) return false;
}
for (var o in obj1) {
switch (true) {
// Catch comparison of element first to prevent infinite loop when caught as objects
case ( _.isElement(obj1[o]) ) :
if (obj1[o] !== obj2[o]) return false;
break;
case ( _.isNaN(obj1[o]) ) :
if (!_.isNaN(obj2[o])) return false;
break;
case ( typeof obj1[o] === "object" ) :
if (!_.isEqual(obj1[o], obj2[o])) return false;
break;
case ( typeof obj1[o] === "function" ) :
if (!_.isFunction(obj2[o])) return false;
if (obj1[o].toString() !== obj2[o].toString()) return false;
break;
default :
if (obj1[o] !== obj2[o]) return false;
}
}
// Reverse comparison of `obj2`
for (var o in obj2) {
if (typeof obj1 === "undefined") return false;
if (obj1 === null || obj1 === undefined) return false;
if (_.isFalsy(obj1[o])) {
if (_.isNaN(obj1[o])) {
if (!_.isNaN(obj2[o])) return false;
} else if (obj1[o] !== obj2[o]) return false;
}
}
}
return true;
};
I'm looking for advice on strategies I can use to improve its performance. My initial thoughts are get rid of my calls to internal library methods? Should I implement a different type of loop. Would a while
lead to any performance gains?
After taking much of what @Joseph the Dreamer said into account I refactored _.isEqual
in a seemingly much more efficient manner, which now compares the properties and values that might be attached to functions as well. As you can see I did away with a great many of my previous comparisons because they're rendered redundant in this iteration (I believe).
Here are the unit tests to back this method up: _.isEqual Unit Tests
The code:
_.isEqual = function (obj1, obj2) {
var type = _.type(obj1), result, o;
switch (true) {
// Not the same TYPE
case type != _.type(obj2) :
return false;
// NaNs
case type == 'nan' :
return _.isNaN(obj1) && _.isNaN(obj2);
// Primitives (types that will compare correctly with ===)
case ((typeof obj1 != 'object' && type != 'function') || type == 'null') :
return obj1 === obj2;
// Functions or Elements
case type == 'function' || type == 'element' :
return obj1.constructor === obj2.constructor;
// JavaScript Objects
default :
if (_.len(obj1) == 0) {
result = _.len(obj2) == 0;
} else {
for (o in obj1) {
if (!(result = _.isEqual(obj1[o], obj2[o]))) break;
}
}
}
return result;
};