eql.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. /*!
  2. * deep-eql
  3. * Copyright(c) 2013 Jake Luer <jake@alogicalparadox.com>
  4. * MIT Licensed
  5. */
  6. /*!
  7. * Module dependencies
  8. */
  9. var type = require('type-detect');
  10. /*!
  11. * Buffer.isBuffer browser shim
  12. */
  13. var Buffer;
  14. try { Buffer = require('buffer').Buffer; }
  15. catch(ex) {
  16. Buffer = {};
  17. Buffer.isBuffer = function() { return false; }
  18. }
  19. /*!
  20. * Primary Export
  21. */
  22. module.exports = deepEqual;
  23. /**
  24. * Assert super-strict (egal) equality between
  25. * two objects of any type.
  26. *
  27. * @param {Mixed} a
  28. * @param {Mixed} b
  29. * @param {Array} memoised (optional)
  30. * @return {Boolean} equal match
  31. */
  32. function deepEqual(a, b, m) {
  33. if (sameValue(a, b)) {
  34. return true;
  35. } else if ('date' === type(a)) {
  36. return dateEqual(a, b);
  37. } else if ('regexp' === type(a)) {
  38. return regexpEqual(a, b);
  39. } else if (Buffer.isBuffer(a)) {
  40. return bufferEqual(a, b);
  41. } else if ('arguments' === type(a)) {
  42. return argumentsEqual(a, b, m);
  43. } else if (!typeEqual(a, b)) {
  44. return false;
  45. } else if (('object' !== type(a) && 'object' !== type(b))
  46. && ('array' !== type(a) && 'array' !== type(b))) {
  47. return sameValue(a, b);
  48. } else {
  49. return objectEqual(a, b, m);
  50. }
  51. }
  52. /*!
  53. * Strict (egal) equality test. Ensures that NaN always
  54. * equals NaN and `-0` does not equal `+0`.
  55. *
  56. * @param {Mixed} a
  57. * @param {Mixed} b
  58. * @return {Boolean} equal match
  59. */
  60. function sameValue(a, b) {
  61. if (a === b) return a !== 0 || 1 / a === 1 / b;
  62. return a !== a && b !== b;
  63. }
  64. /*!
  65. * Compare the types of two given objects and
  66. * return if they are equal. Note that an Array
  67. * has a type of `array` (not `object`) and arguments
  68. * have a type of `arguments` (not `array`/`object`).
  69. *
  70. * @param {Mixed} a
  71. * @param {Mixed} b
  72. * @return {Boolean} result
  73. */
  74. function typeEqual(a, b) {
  75. return type(a) === type(b);
  76. }
  77. /*!
  78. * Compare two Date objects by asserting that
  79. * the time values are equal using `saveValue`.
  80. *
  81. * @param {Date} a
  82. * @param {Date} b
  83. * @return {Boolean} result
  84. */
  85. function dateEqual(a, b) {
  86. if ('date' !== type(b)) return false;
  87. return sameValue(a.getTime(), b.getTime());
  88. }
  89. /*!
  90. * Compare two regular expressions by converting them
  91. * to string and checking for `sameValue`.
  92. *
  93. * @param {RegExp} a
  94. * @param {RegExp} b
  95. * @return {Boolean} result
  96. */
  97. function regexpEqual(a, b) {
  98. if ('regexp' !== type(b)) return false;
  99. return sameValue(a.toString(), b.toString());
  100. }
  101. /*!
  102. * Assert deep equality of two `arguments` objects.
  103. * Unfortunately, these must be sliced to arrays
  104. * prior to test to ensure no bad behavior.
  105. *
  106. * @param {Arguments} a
  107. * @param {Arguments} b
  108. * @param {Array} memoize (optional)
  109. * @return {Boolean} result
  110. */
  111. function argumentsEqual(a, b, m) {
  112. if ('arguments' !== type(b)) return false;
  113. a = [].slice.call(a);
  114. b = [].slice.call(b);
  115. return deepEqual(a, b, m);
  116. }
  117. /*!
  118. * Get enumerable properties of a given object.
  119. *
  120. * @param {Object} a
  121. * @return {Array} property names
  122. */
  123. function enumerable(a) {
  124. var res = [];
  125. for (var key in a) res.push(key);
  126. return res;
  127. }
  128. /*!
  129. * Simple equality for flat iterable objects
  130. * such as Arrays or Node.js buffers.
  131. *
  132. * @param {Iterable} a
  133. * @param {Iterable} b
  134. * @return {Boolean} result
  135. */
  136. function iterableEqual(a, b) {
  137. if (a.length !== b.length) return false;
  138. var i = 0;
  139. var match = true;
  140. for (; i < a.length; i++) {
  141. if (a[i] !== b[i]) {
  142. match = false;
  143. break;
  144. }
  145. }
  146. return match;
  147. }
  148. /*!
  149. * Extension to `iterableEqual` specifically
  150. * for Node.js Buffers.
  151. *
  152. * @param {Buffer} a
  153. * @param {Mixed} b
  154. * @return {Boolean} result
  155. */
  156. function bufferEqual(a, b) {
  157. if (!Buffer.isBuffer(b)) return false;
  158. return iterableEqual(a, b);
  159. }
  160. /*!
  161. * Block for `objectEqual` ensuring non-existing
  162. * values don't get in.
  163. *
  164. * @param {Mixed} object
  165. * @return {Boolean} result
  166. */
  167. function isValue(a) {
  168. return a !== null && a !== undefined;
  169. }
  170. /*!
  171. * Recursively check the equality of two objects.
  172. * Once basic sameness has been established it will
  173. * defer to `deepEqual` for each enumerable key
  174. * in the object.
  175. *
  176. * @param {Mixed} a
  177. * @param {Mixed} b
  178. * @return {Boolean} result
  179. */
  180. function objectEqual(a, b, m) {
  181. if (!isValue(a) || !isValue(b)) {
  182. return false;
  183. }
  184. if (a.prototype !== b.prototype) {
  185. return false;
  186. }
  187. var i;
  188. if (m) {
  189. for (i = 0; i < m.length; i++) {
  190. if ((m[i][0] === a && m[i][1] === b)
  191. || (m[i][0] === b && m[i][1] === a)) {
  192. return true;
  193. }
  194. }
  195. } else {
  196. m = [];
  197. }
  198. try {
  199. var ka = enumerable(a);
  200. var kb = enumerable(b);
  201. } catch (ex) {
  202. return false;
  203. }
  204. ka.sort();
  205. kb.sort();
  206. if (!iterableEqual(ka, kb)) {
  207. return false;
  208. }
  209. m.push([ a, b ]);
  210. var key;
  211. for (i = ka.length - 1; i >= 0; i--) {
  212. key = ka[i];
  213. if (!deepEqual(a[key], b[key], m)) {
  214. return false;
  215. }
  216. }
  217. return true;
  218. }