kota's memex

node assert.equal

This is probably more reliable than either of these.

recursive implementation

The main drawback with the recursive implementation is that it will cause a stack overflow with large objects. Simply pass it an array with 30,000 elements and watch it crash. I checked deno's deep equal implementation and it also suffers from this. I don't know if node's has this issue.

function deepEqual(x, y) {
  let tx = typeof x;
  let ty = typeof y;
  if (tx !== "object" && ty !== "object") {
    // Early exit for non-objects.
    if (x === y) {
      return true;
    }
  } else if (x === null || y === null) {
    // Check for typeof(null) = "object" edgecase.
    if (x === y) {
      return true;
    } else {
      return false; // Avoid object checking logic.
    }
  } else {
    // Both are non-null objects. Compare keys.
    let xkeys = Object.keys(x);
    let ykeys = Object.keys(y);
    if (xkeys.length !== ykeys.length) {
      // Early exit for differing key lengths.
      return false;
    }

    // Walk keys recursively.
		for (let key of xkeys) {
			if (!ykeys.includes(key) || !deepEqual(x[key], y[key])) return false;
		}
    return true; // All keys matched.
  }
  return false;
}

iterative implementation

This one is a bit more complicated, but can handle massive objects. I'm not sure how the speed compares to the recursive implementation.

function deepEqual(x, y) {
  let tx = typeof x;
  let ty = typeof y;
  // Early exit for primitive equality.
  if (x === y) {
    return true;
  }

  // Handle null and non-object cases.
  if (x === null || y === null || tx !== "object" || ty !== "object") {
    return false;
  }

  // Create iteration stack and compare objects.
  let xstack = [x];
  let ystack = [y];
  for (let i = 0; i < xstack.length; i++) {
    // Check current stack objects.
    let xx = xstack[i];
    let yy = ystack[i];
    let xkeys = Object.keys(xx);
    let ykeys = Object.keys(yy);
    if (xkeys.length !== ykeys.length) {
      // Key length mismatch early exit.
      return false;
    }

    // Check if we need to search sub-children.
    for (let ii = 0; ii < xkeys.length; ii++) {
      let key = xkeys[ii];
      if (!yy.hasOwnProperty(key)) {
        // Easier than .includes on keys
        return false;
      }
      let xchild = xx[key];
      let ychild = yy[key];
      if (xchild !== ychild) {
        // Handle null and non-object cases.
        if (
          xchild === null ||
          ychild === null ||
          typeof xchild !== "object" ||
          typeof ychild !== "object"
        ) {
          return false;
        }
        // Add children to the stacks.
        xstack.push(xchild);
        ystack.push(ychild);
      }
    }
  }
  return true; // Search completed without inequality.
}