deepDiffer.js 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. /**
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. *
  7. * @format
  8. * @flow
  9. */
  10. 'use strict';
  11. let logListeners;
  12. type LogListeners = {|
  13. +onDifferentFunctionsIgnored: (nameOne: ?string, nameTwo: ?string) => void,
  14. |};
  15. type Options = {|+unsafelyIgnoreFunctions?: boolean|};
  16. function unstable_setLogListeners(listeners: ?LogListeners) {
  17. logListeners = listeners;
  18. }
  19. /*
  20. * @returns {bool} true if different, false if equal
  21. */
  22. const deepDiffer = function(
  23. one: any,
  24. two: any,
  25. maxDepthOrOptions: Options | number = -1,
  26. maybeOptions?: Options,
  27. ): boolean {
  28. const options =
  29. typeof maxDepthOrOptions === 'number' ? maybeOptions : maxDepthOrOptions;
  30. const maxDepth =
  31. typeof maxDepthOrOptions === 'number' ? maxDepthOrOptions : -1;
  32. if (maxDepth === 0) {
  33. return true;
  34. }
  35. if (one === two) {
  36. // Short circuit on identical object references instead of traversing them.
  37. return false;
  38. }
  39. if (typeof one === 'function' && typeof two === 'function') {
  40. // We consider all functions equal unless explicitly configured otherwise
  41. let unsafelyIgnoreFunctions = options?.unsafelyIgnoreFunctions;
  42. if (unsafelyIgnoreFunctions == null) {
  43. if (
  44. logListeners &&
  45. logListeners.onDifferentFunctionsIgnored &&
  46. (!options || !('unsafelyIgnoreFunctions' in options))
  47. ) {
  48. logListeners.onDifferentFunctionsIgnored(one.name, two.name);
  49. }
  50. unsafelyIgnoreFunctions = true;
  51. }
  52. return !unsafelyIgnoreFunctions;
  53. }
  54. if (typeof one !== 'object' || one === null) {
  55. // Primitives can be directly compared
  56. return one !== two;
  57. }
  58. if (typeof two !== 'object' || two === null) {
  59. // We know they are different because the previous case would have triggered
  60. // otherwise.
  61. return true;
  62. }
  63. if (one.constructor !== two.constructor) {
  64. return true;
  65. }
  66. if (Array.isArray(one)) {
  67. // We know two is also an array because the constructors are equal
  68. const len = one.length;
  69. if (two.length !== len) {
  70. return true;
  71. }
  72. for (let ii = 0; ii < len; ii++) {
  73. if (deepDiffer(one[ii], two[ii], maxDepth - 1, options)) {
  74. return true;
  75. }
  76. }
  77. } else {
  78. for (const key in one) {
  79. if (deepDiffer(one[key], two[key], maxDepth - 1, options)) {
  80. return true;
  81. }
  82. }
  83. for (const twoKey in two) {
  84. // The only case we haven't checked yet is keys that are in two but aren't
  85. // in one, which means they are different.
  86. if (one[twoKey] === undefined && two[twoKey] !== undefined) {
  87. return true;
  88. }
  89. }
  90. }
  91. return false;
  92. };
  93. module.exports = deepDiffer;
  94. module.exports.unstable_setLogListeners = unstable_setLogListeners;