jsPerf.app is an online JavaScript performance benchmark test runner & jsperf.com mirror. It is a complete rewrite in homage to the once excellent jsperf.com now with hopefully a more modern & maintainable codebase.
jsperf.com URLs are mirrored at the same path, e.g:
https://jsperf.com/negative-modulo/2
Can be accessed at:
https://jsperf.app/negative-modulo/2
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.5.0/lodash.min.js"></script>// @ts-nocheck
// 1. Deeply nested objects (tests recursion depth)
const deepNested1 = {a: {b: {c: {d: {e: {f: {g: {h: {i: {j: {k: 1}}}}}}}}}}};
const deepNested2 = {a: {b: {c: {d: {e: {f: {g: {h: {i: {j: {k: 1}}}}}}}}}}};
const deepNested3 = {a: {b: {c: {d: {e: {f: {g: {h: {i: {j: {k: 2}}}}}}}}}}};
// 2. Wide objects (many keys at same level)
const wide1 = Object.fromEntries(Array.from({length: 1000}, (_, i) => [`key${i}`, i]));
const wide2 = Object.fromEntries(Array.from({length: 1000}, (_, i) => [`key${i}`, i]));
const wide3 = {...wide2, key999: 'different'};
// 3. Large arrays
const largeArr1 = Array.from({length: 10000}, (_, i) => ({id: i, value: `item${i}`}));
const largeArr2 = Array.from({length: 10000}, (_, i) => ({id: i, value: `item${i}`}));
const largeArr3 = [...largeArr2.slice(0, 9999), {id: 9999, value: 'changed'}];
// 4. Mixed types with nulls/undefined (tests your null handling)
const mixed1 = {
str: 'hello',
num: 42,
bool: true,
nil: null,
undef: undefined,
date: new Date('2026-01-08'),
arr: [1, null, undefined, {nested: null}],
obj: {a: null, b: undefined, c: {d: null}}
};
const mixed2 = {
str: 'hello',
num: 42,
bool: true,
nil: undefined, // null vs undefined
undef: null, // undefined vs null
date: new Date('2026-01-08'),
arr: [1, undefined, null, {nested: undefined}], // swapped
obj: {a: undefined, b: null, c: {d: undefined}} // swapped
};
// 5. Sparse vs dense (missing keys = undefined)
const sparse1 = {a: 1, c: 3, e: 5};
const sparse2 = {a: 1, b: undefined, c: 3, d: null, e: 5};
// 6. Worst case: identical large nested structure
const createDeepWide = (depth, width) => {
if (depth === 0) return {value: Math.random()};
return Object.fromEntries(
Array.from({length: width}, (_, i) => [`child${i}`, createDeepWide(depth - 1, width)])
);
};
const deepWide1 = createDeepWide(5, 10); // 10^5 = 100,000 leaf nodes
const deepWide2 = JSON.parse(JSON.stringify(deepWide1)); // clone
// [name, obj1, obj2, iterations, expectedResult]
const testSets = [
[deepNested1, deepNested2],
[deepNested1, deepNested3],
[wide1, wide2],
[ wide1, wide3],
[ largeArr1, largeArr2],
[ largeArr1, largeArr3],
[ mixed1, mixed2],
[ sparse1, sparse2],
[deepWide1, deepWide2],
];
function isEquals(obj1, obj2, options = {}) {
const {treatNullsAndUndefinedAsEqual = true} = options;
// Direct comparison for primitives and same references
if (obj1 === obj2) {
return true;
}
// Handle null/undefined comparison based on options
const obj1IsNullish = obj1 === null || obj1 === undefined;
const obj2IsNullish = obj2 === null || obj2 === undefined;
if (obj1IsNullish && obj2IsNullish) {
return treatNullsAndUndefinedAsEqual ? true : obj1 === obj2;
}
// One is null/undefined, the other is not — not equal
if (obj1IsNullish || obj2IsNullish) {
return false;
}
// Date comparison
if (obj1 instanceof Date && obj2 instanceof Date) {
return obj1.getTime() === obj2.getTime();
}
// If either is not an object, they're not equal (primitives would have matched above)
if (typeof obj1 !== 'object' || typeof obj2 !== 'object') {
return false;
}
// Array type mismatch
const obj1IsArray = Array.isArray(obj1);
const obj2IsArray = Array.isArray(obj2);
if (obj1IsArray !== obj2IsArray) {
return false;
}
// Compare keys from obj1 — missing keys in obj2 will be undefined
for (const key of Object.keys(obj1)) {
if (!isEquals(obj1[key], obj2[key], options)) {
return false;
}
}
// Compare keys from obj2 — missing keys in obj1 will be undefined
for (const key of Object.keys(obj2)) {
if (!isEquals(obj1[key], obj2[key], options)) {
return false;
}
}
return true;
}
function isEquals2(obj1, obj2, options= {}){
const treatNullsAndUndefinedAsEqual = options.treatNullsAndUndefinedAsEqual ?? true;
return isEqualsInternal(obj1, obj2, treatNullsAndUndefinedAsEqual);
}
function isEqualsInternal(obj1, obj2, treatNullsAndUndefinedAsEqual){
// Direct comparison for primitives and same references
if (obj1 === obj2) {
return true;
}
// Handle null/undefined comparison
const obj1IsNullish = obj1 === null || obj1 === undefined;
const obj2IsNullish = obj2 === null || obj2 === undefined;
if (obj1IsNullish && obj2IsNullish) {
return treatNullsAndUndefinedAsEqual || obj1 === obj2;
}
if (obj1IsNullish || obj2IsNullish) {
return false;
}
// Date comparison
if (obj1 instanceof Date && obj2 instanceof Date) {
return obj1.getTime() === obj2.getTime();
}
// If either is not an object, they're not equal
if (typeof obj1 !== 'object' || typeof obj2 !== 'object') {
return false;
}
// Array comparison with length check first
const obj1IsArray = Array.isArray(obj1);
const obj2IsArray = Array.isArray(obj2);
if (obj1IsArray !== obj2IsArray) {
return false;
}
if (obj1IsArray) {
// Early exit if lengths differ
if (obj1.length !== obj2.length) {
return false;
}
for (let i = 0; i < obj1.length; i++) {
if (!isEqualsInternal(obj1[i], obj2[i], treatNullsAndUndefinedAsEqual)) {
return false;
}
}
return true;
}
// Object comparison
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
// Fast path: when not treating nulls as equal, key counts must match
if (!treatNullsAndUndefinedAsEqual && keys1.length !== keys2.length) {
return false;
}
// Compare all keys from obj1
for (const key of keys1) {
if (!isEqualsInternal(obj1[key], obj2[key], treatNullsAndUndefinedAsEqual)) {
return false;
}
}
// Only check extra keys in obj2 (keys not in obj1)
// This avoids comparing shared keys twice
if (keys2.length > keys1.length || treatNullsAndUndefinedAsEqual) {
for (const key of keys2) {
if (!(key in obj1)) {
// Key only exists in obj2 - compare against undefined
if (!isEqualsInternal(undefined, obj2[key], treatNullsAndUndefinedAsEqual)) {
return false;
}
}
}
}
return true;
}
Ready to run.
| Test | Ops/sec | |
|---|---|---|
| comparison with "lodash.isEqual" | | ready |
| comparison with "isEquals" (manual function) | | ready |
| comparison with "isEquals2" (manual function) | | ready |
You can edit these tests or add more tests to this page by appending /edit to the URL.