Lodash isEqual vs vanilla deepEqual (v296)

Revision 296 of this benchmark created on


Preparation HTML

<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>

Setup

function deepEqual(obj1, obj2, visited = new WeakMap()) {
  if (obj1 === obj2) {
    return true;
  }

  if (obj1 == null || obj2 == null) {
    return obj1 === obj2;
  }

  if (typeof obj1 !== typeof obj2) {
    return false;
  }

  if (obj1 instanceof Date && obj2 instanceof Date) {
    return obj1.getTime() === obj2.getTime();
  }

  if (obj1 instanceof RegExp && obj2 instanceof RegExp) {
    return obj1.toString() === obj2.toString();
  }

  if (typeof obj1 === 'function' && typeof obj2 === 'function') {
    return obj1.toString() === obj2.toString();
  }

  if (Array.isArray(obj1) && Array.isArray(obj2)) {
    if (obj1.length !== obj2.length) {
      return false;
    }
    for (let i = 0; i < obj1.length; i++) {
      if (!deepEqual(obj1[i], obj2[i], visited)) {
        return false;
      }
    }
    return true;
  }

  if (obj1 instanceof Map && obj2 instanceof Map) {
    if (obj1.size !== obj2.size) {
      return false;
    }
    for (const [key, value] of obj1) {
      if (!obj2.has(key) || !deepEqual(value, obj2.get(key), visited)) {
        return false;
      }
    }
    return true;
  }

  if (obj1 instanceof Set && obj2 instanceof Set) {
    if (obj1.size !== obj2.size) {
      return false;
    }
    for (const item of obj1) {
      if (!obj2.has(item)) {
        return false;
      }
    }
    return true;
  }

  if (ArrayBuffer.isView(obj1) && ArrayBuffer.isView(obj2)) {
    if (obj1.byteLength !== obj2.byteLength) {
      return false;
    }
    return Array.prototype.every.call(obj1, (val, i) => val === obj2[i]);
  }

  if (typeof obj1 === 'object') {
    if (visited.has(obj1)) {
      return visited.get(obj1) === obj2;
    }
    visited.set(obj1, obj2);

    const keys1 = Object.keys(obj1);
    const keys2 = Object.keys(obj2);

    if (keys1.length !== keys2.length) {
      return false;
    }

    for (const key of keys1) {
      if (!keys2.includes(key)) {
        return false;
      }
      if (!deepEqual(obj1[key], obj2[key], visited)) {
        return false;
      }
    }

    return true;
  }

  return false;
}
    
function generateRandomObject(objectSize, maxDepth) {
  const KB = 1024;
  const targetSize = objectSize * KB;
  let currentSize = 0;

  function getRandomKey() {
    return Math.random().toString(36).substring(2, 15);
  }

  function getRandomPrimitive() {
    const primitives = [
      Math.random().toString(36).substring(2, 15), // string
      Math.floor(Math.random() * 100), // number
      Math.random() < 0.5, // boolean
      null // null
    ];
    return primitives[Math.floor(Math.random() * primitives.length)];
  }

  function getRandomValue(depth) {
    if (depth >= maxDepth) {
      return getRandomPrimitive();
    }

    const types = ['object', 'array', 'primitive'];
    const type = types[Math.floor(Math.random() * types.length)];

    switch (type) {
      case 'object':
        return generateRandomObjectHelper(depth + 1);
      case 'array':
        return Array.from({ length: Math.floor(Math.random() * 5) + 1 }, () => getRandomValue(depth + 1));
      case 'primitive':
        return getRandomPrimitive();
    }
  }

  function generateRandomObjectHelper(depth) {
    const obj = {};
    while (currentSize < targetSize) {
      for (let i = 0; i < 10; i++) { // Increase the number of keys per level
        const key = getRandomKey();
        const value = getRandomValue(depth);
        obj[key] = value;
        currentSize += key.length + JSON.stringify(value).length;
        if (currentSize >= targetSize) break;
      }
    }
    return obj;
  }

  return generateRandomObjectHelper(0);
}

var smallObj = generateRandomObject(1, 3);
var smallObjCpy = _.cloneDeep(smallObj)
var smallObj2 = generateRandomObject(1, 3);
var medObj = generateRandomObject(3, 6);
var medObjCpy = _.cloneDeep(medObj)
var medObj2 = generateRandomObject(3, 6);
var largeObj = generateRandomObject(5, 8);
var largeObjCpy = _.cloneDeep(largeObj)
var largeObj2 = generateRandomObject(5, 8);

Test runner

Ready to run.

Testing in
TestOps/sec
Lodash isEqual - Small (Equal)
_.isEqual(smallObj, smallObjCpy)
ready
Vanilla deepEqual - Small (Equal)
deepEqual(smallObj, smallObjCpy)
ready
Lodash isEqual - Medium (Equal)
_.isEqual(medObj, medObjCpy)
ready
Vanilla deepEqual - Medium (Equal)
deepEqual(medObj, medObjCpy)
ready
Lodash isEqual - Large (Equal)
_.isEqual(largeObj, largeObjCpy)
ready
Vanilla deepEqual - Large (Equal)
deepEqual(largeObj, largeObjCpy)
ready
Lodash isEqual - Small (Not equal)
_.isEqual(smallObj, smallObj2)
ready
Vanilla deepEqual - Small (Not equal)
deepEqual(smallObj, smallObj2)
ready
Lodash isEqual - Medium (Not equal)
_.isEqual(medObj, medObj2)
ready
Vanilla deepEqual - Medium (Not equal)
deepEqual(medObj, medObj2)
ready
Lodash isEqual - Large (Not equal)
_.isEqual(largeObj, largeObj2)
ready
Vanilla deepEqual - Large (Not equal)
deepEqual(largeObj, largeObj2)
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.