I noticed that all the deep equality implementations I've found are using recursion and theoretically the iterative form should be faster. However, it's a bit slower for me and I don't understand why.
Assume the data is the result of JSON.parse
(i.e. primitives, plain objects, and arrays).
Recursive:
function equals1(x, y) {
if (x === y) return true;
if (Array.isArray(x) && Array.isArray(y)) {
if (x.length !== y.length) return false;
for (let i = 0; i < x.length; i++) {
if (!equals1(x[i], y[i])) return false;
}
return true;
}
if ((typeof x !== 'object') || (typeof y !== 'object')) return false;
const xKeys = Object.keys(x);
const yKeys = Object.keys(y);
if (xKeys.length !== yKeys.length) return false;
for (const k of xKeys) {
if (!y.hasOwnProperty(k)) return false;
if (!equals1(x[k], y[k])) return false;
}
return true;
}
Iterative:
function equals2(a, b) {
const stack = [a, b];
let idx = 2;
while (idx > 0) {
const x = stack[idx - 1];
const y = stack[idx - 2];
idx -= 2;
if (x === y) continue;
if (Array.isArray(x) && Array.isArray(y)) {
if (x.length !== y.length) return false;
for (let i = 0; i < x.length; i++) {
idx += 2;
if (idx > stack.length) stack.push(x[i], y[i]);
else {
stack[idx - 1] = x[i];
stack[idx - 2] = y[i];
}
}
} else {
if ((typeof x !== 'object') || (typeof y !== 'object')) return false;
const xKeys = Object.keys(x);
const yKeys = Object.keys(y);
if (xKeys.length !== yKeys.length) return false;
for (const k of xKeys) {
if (!y.hasOwnProperty(k)) return false;
idx += 2;
if (idx > stack.length) stack.push(x[k], y[k]);
else {
stack[idx - 1] = x[k];
stack[idx - 2] = y[k];
}
}
}
}
return true;
}
I'm using the index instead of the traditional stack.pop
approach because it's slightly faster.
JSPerf: https://jsperf.com/deep-object-compare-123/1
The data is from Reddit: https://www.reddit.com/r/javascript.json
For me, the iterative version is 20-25% slower on Chrome and Edge, and the same speed on Firefox. I tried pre-allocating the stack array and removing the continue
, but it didn't change the results. As far as I know, JS engines can optimize tail-recursive functions, but this isn't tail-recursive.
Any ideas what's going on?