is-deep-equal.js 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071
  1. /**
  2. * Is deep equal.
  3. * https://github.com/pinglu85/BFEdevSolutions/blob/main/Coding-Problems/69.implement-deep-equal-isEqual.md
  4. *
  5. * @param {*} firstValue
  6. * @param {*} secondValue
  7. * @returns {boolean}
  8. */
  9. export function isDeepEqual(firstValue, secondValue) {
  10. const cached = new WeakMap();
  11. const compare = (a, b) => {
  12. // Check if one of the two inputs is primitive by using typeof
  13. // operator; since the typeof primitive null is object, check
  14. // if one of the inputs is equal to null. If one of the two
  15. // inputs is primitive, then I can compare them by reference.
  16. if (a === null || b === null) return a === b;
  17. if (typeof a !== 'object' || typeof b !== 'object') return a === b;
  18. // Check if the data type of the two inputs are the same,
  19. // both are arrays or objects. If they are not, return false.
  20. const dataTypeA = Array.isArray(a) ? 'array' : 'object';
  21. const dataTypeB = Array.isArray(b) ? 'array' : 'object';
  22. if (dataTypeA !== dataTypeB) return false;
  23. // Use Object.keys and Object.getOwnPropertySymbols to get
  24. // all of enumerable and not-inherited properties of the two
  25. // inputs. Compare their size respectively, if one of them
  26. // is not equal, return false.
  27. const keysA = Object.keys(a);
  28. const keysB = Object.keys(b);
  29. if (keysA.length !== keysB.length) return false;
  30. const symbolsA = Object.getOwnPropertySymbols(a);
  31. const symbolsB = Object.getOwnPropertySymbols(b);
  32. if (symbolsA.length !== symbolsB.length) return false;
  33. // To handle the circular reference, initialize a WeakMap
  34. // that is going to keep track of the objects or arrays
  35. // that have been seen, in which each key is an object
  36. // or an array and each value is a set of objects or arrays,
  37. // that have been compared to that object or array.
  38. let setForA = cached.get(a);
  39. if (setForA == null) {
  40. setForA = new Set();
  41. cached.set(a, setForA);
  42. } else if (setForA.has(b)) {
  43. return true;
  44. }
  45. setForA.add(b);
  46. let setForB = cached.get(b);
  47. if (setForB == null) {
  48. setForB = new Set();
  49. cached.set(b, setForB);
  50. } else if (setForB.has(a)) {
  51. return true;
  52. }
  53. setForB.add(a);
  54. // Compare the property names and the values. Loop through
  55. // all the properties of the first input data, check if
  56. // the property name also exist in the second input data,
  57. // if not, return false; otherwise recursively compare
  58. // the property value.
  59. const propertyNamesA = [...keysA, ...symbolsA];
  60. for (const propertyNameA of propertyNamesA) {
  61. if (!Object.prototype.hasOwnProperty.call(b, propertyNameA)) return false;
  62. const propertyValueA = a[propertyNameA];
  63. const propertyValueB = b[propertyNameA];
  64. if (!compare(propertyValueA, propertyValueB)) return false;
  65. }
  66. // If we get out of the loop without
  67. // returning false, return true.
  68. return true;
  69. };
  70. return compare(firstValue, secondValue);
  71. }