fast-equals 5.0.0-beta.5 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +17 -1
  2. package/README.md +68 -48
  3. package/dist/cjs/index.cjs +165 -140
  4. package/dist/cjs/index.cjs.map +1 -1
  5. package/dist/cjs/types/comparator.d.ts +20 -3
  6. package/dist/cjs/types/equals.d.ts +5 -2
  7. package/dist/cjs/types/index.d.ts +9 -9
  8. package/dist/cjs/types/internalTypes.d.ts +100 -12
  9. package/dist/cjs/types/utils.d.ts +1 -6
  10. package/dist/esm/index.mjs +165 -140
  11. package/dist/esm/index.mjs.map +1 -1
  12. package/dist/esm/types/comparator.d.ts +20 -3
  13. package/dist/esm/types/equals.d.ts +5 -2
  14. package/dist/esm/types/index.d.ts +9 -9
  15. package/dist/esm/types/internalTypes.d.ts +100 -12
  16. package/dist/esm/types/utils.d.ts +1 -6
  17. package/dist/min/index.js +1 -1
  18. package/dist/min/types/comparator.d.ts +20 -3
  19. package/dist/min/types/equals.d.ts +5 -2
  20. package/dist/min/types/index.d.ts +9 -9
  21. package/dist/min/types/internalTypes.d.ts +100 -12
  22. package/dist/min/types/utils.d.ts +1 -6
  23. package/dist/umd/index.js +165 -140
  24. package/dist/umd/index.js.map +1 -1
  25. package/dist/umd/types/comparator.d.ts +20 -3
  26. package/dist/umd/types/equals.d.ts +5 -2
  27. package/dist/umd/types/index.d.ts +9 -9
  28. package/dist/umd/types/internalTypes.d.ts +100 -12
  29. package/dist/umd/types/utils.d.ts +1 -6
  30. package/package.json +7 -7
  31. package/recipes/legacy-circular-equal-support.md +3 -2
  32. package/recipes/legacy-regexp-support.md +2 -3
  33. package/recipes/non-standard-properties.md +4 -2
  34. package/src/comparator.ts +126 -38
  35. package/src/equals.ts +97 -63
  36. package/src/index.ts +21 -65
  37. package/src/internalTypes.ts +104 -16
  38. package/src/utils.ts +2 -24
package/CHANGELOG.md CHANGED
@@ -4,14 +4,30 @@
4
4
 
5
5
  ### Breaking changes
6
6
 
7
+ #### `constructor` equality now required
8
+
9
+ To align with other implementations common in the community, but also to be more functionally correct, the two objects being compared now must have equal `constructor`s.
10
+
11
+ #### `Map` / `Set` comparisons no longer support IE11
12
+
13
+ In previous verisons, `.forEach()` was used to ensure that support for `Symbol` was not required, as IE11 did not have `Symbol` and therefore both `Map` and `Set` did not have iterator-based methods such as `.values()` or `.entries()`. Since IE11 is no longer a supported browser, and support for those methods is present in all browsers and Node for quite a while, the comparison has moved to use these methods. This results in a ~20% performance increase.
14
+
7
15
  #### `createCustomEqual` contract has changed
8
16
 
9
- To better facilitate strict comparisons, but also to allow for `meta` use separate from caching, the contract for `createCustomEqual` has changed. See the [README documentation](./README.md#createcustomequal) for more details.
17
+ To better facilitate strict comparisons, but also to allow for `meta` use separate from caching, the contract for `createCustomEqual` has changed. See the [README documentation](./README.md#createcustomequal) for more details, but froma high-level:
18
+
19
+ - `meta` is no longer passed through to equality comparators, but rather a general `state` object which contains `meta`
20
+ - `cache` now also lives on the `state` object, which allows for use of the `meta` property separate from but in parallel with the circular cache
21
+ - `equals` is now on `state`, which prevents the need to pass through the separate `isEqual` method for the equality comparator
10
22
 
11
23
  #### `createCustomCircularEqual` has been removed
12
24
 
13
25
  You can create a custom circular equality comparator through `createCustomEqual` now by providing `circular: true` to the options.
14
26
 
27
+ #### Custom `meta` values are no longer passed at callsite
28
+
29
+ To use `meta` properties for comparisons, they must be returned in a `createState` method.
30
+
15
31
  #### Deep links have changed
16
32
 
17
33
  If you were deep-linking into a specific asset type (ESM / CJS / UMD), they have changed location.
package/README.md CHANGED
@@ -4,18 +4,21 @@
4
4
  <img src="https://img.shields.io/badge/coverage-100%25-brightgreen.svg"/>
5
5
  <img src="https://img.shields.io/badge/license-MIT-blue.svg"/>
6
6
 
7
- Perform [blazing fast](#benchmarks) equality comparisons (either deep or shallow) on two objects passed. It has no dependencies, and is ~1.74kB when minified and gzipped.
7
+ Perform [blazing fast](#benchmarks) equality comparisons (either deep or shallow) on two objects passed, while also maintaining a high degree of flexibility for various implementation use-cases. It has no dependencies, and is ~1.8kB when minified and gzipped.
8
8
 
9
- Unlike most equality validation libraries, the following types are handled out-of-the-box:
9
+ The following types are handled out-of-the-box:
10
10
 
11
- - `NaN`
11
+ - Plain objects (including `react` elements and `Arguments`)
12
+ - Arrays
13
+ - Typed Arrays
12
14
  - `Date` objects
13
15
  - `RegExp` objects
14
16
  - `Map` / `Set` iterables
15
17
  - `Promise` objects
16
- - `react` elements
18
+ - Primitive wrappers (`new Boolean()` / `new Number()` / `new String()`)
19
+ - Custom class instances, including subclasses of native classes
17
20
 
18
- Circular object and strict comparisons are supported, as is creation of a custom equality comparator for specific use-cases.
21
+ Methods are available for deep, shallow, or referential equality comparison. In addition, you can opt into support for circular objects, or performing a "strict" comparison with unconventional property definition, or both. You can also customize any specific type comparison based on your application's use-cases.
19
22
 
20
23
  ## Table of contents
21
24
 
@@ -162,7 +165,7 @@ console.log(circularShallowEqual(array, [array])); // false
162
165
 
163
166
  Performs the same comparison as `deepEqual` but performs a strict comparison of the objects. In this includes:
164
167
 
165
- - Checking constructors match
168
+ - Checking symbol properties
166
169
  - Checking non-enumerable properties in object comparisons
167
170
  - Checking full descriptor of properties on the object to match
168
171
  - Checking non-index properties on arrays
@@ -269,24 +272,13 @@ Creates a custom equality comparator that will be used on nested values in the o
269
272
  The signature is as follows:
270
273
 
271
274
  ```ts
272
- interface BaseCircular extends Pick<WeakMap<any, any>, 'delete' | 'get'> {
273
- set(key: object, value: any): any;
274
- }
275
-
276
- interface DefaultState<Meta> {
277
- readonly cache: undefined | BaseCircular;
278
- readonly equals: InternalEqualityComparator<Meta>;
279
- meta: Meta;
280
- readonly strict: boolean;
275
+ interface Cache<Key extends object, Value> {
276
+ delete(key: Key): boolean;
277
+ get(key: Key): Value | undefined;
278
+ set(key: Key, value: any): any;
281
279
  }
282
280
 
283
- type EqualityComparator<Meta> = <A, B>(
284
- a: A,
285
- b: B,
286
- state: State<Meta>,
287
- ) => boolean;
288
-
289
- interface CreateComparatorCreatorOptions<Meta> {
281
+ interface ComparatorConfig<Meta> {
290
282
  areArraysEqual: TypeEqualityComparator<any[], Meta>;
291
283
  areDatesEqual: TypeEqualityComparator<Date, Meta>;
292
284
  areMapsEqual: TypeEqualityComparator<Map<any, any>, Meta>;
@@ -300,20 +292,25 @@ interface CreateComparatorCreatorOptions<Meta> {
300
292
  areTypedArraysEqual: TypeEqualityComparatory<TypedArray, Meta>;
301
293
  }
302
294
 
303
- interface CustomEqualCreatorOptions<Meta> {
295
+ function createCustomEqual<Meta>(options: {
304
296
  circular?: boolean;
305
- comparator?: EqualityComparator<Meta>;
306
- createCustomConfig?: CreateCustomComparatorConfig<Meta>;
307
- createInternalComparator?: <Meta>(
308
- compare: EqualityComparator<Meta>,
309
- ) => InternalEqualityComparator<Meta>;
310
- createState?: CreateState<Meta>;
297
+ createCustomConfig?: (
298
+ defaultConfig: ComparatorConfig<Meta>,
299
+ ) => Partial<ComparatorConfig<Meta>>;
300
+ createInternalComparator?: (
301
+ compare: <A, B>(a: A, b: B, state: State<Meta>) => boolean,
302
+ ) => (
303
+ a: any,
304
+ b: any,
305
+ indexOrKeyA: any,
306
+ indexOrKeyB: any,
307
+ parentA: any,
308
+ parentB: any,
309
+ state: State<Meta>,
310
+ ) => boolean;
311
+ createState?: () => { cache?: Cache; meta?: Meta };
311
312
  strict?: boolean;
312
- }
313
-
314
- function createCustomEqual(
315
- options: CustomEqualCreatorOptions<Meta>,
316
- ): <A, B>(a: A, b: B, metaOverride?: Meta) => boolean;
313
+ }): <A, B>(a: A, b: B, metaOverride?: Meta) => boolean;
317
314
  ```
318
315
 
319
316
  Create a custom equality comparator. This allows complete control over building a bespoke equality method, in case your use-case requires a higher degree of performance, legacy environment support, or any other non-standard usage. The [recipes](#recipes) provide examples of use in different use-cases, but if you have a specific goal in mind and would like assistance feel free to [file an issue](https://github.com/planttheidea/fast-equals/issues).
@@ -333,7 +330,7 @@ Some recipes have been created to provide examples of use-cases for `createCusto
333
330
 
334
331
  ## Benchmarks
335
332
 
336
- All benchmarks were performed on an i7-8650U Ubuntu Linux laptop with 24GB of memory using NodeJS version `12.19.1`, and are based on averages of running comparisons based deep equality on the following object types:
333
+ All benchmarks were performed on an i9-11900H Ubuntu Linux 22.04 laptop with 64GB of memory using NodeJS version `16.14.2`, and are based on averages of running comparisons based deep equality on the following object types:
337
334
 
338
335
  - Primitives (`String`, `Number`, `null`, `undefined`)
339
336
  - `Function`
@@ -344,19 +341,42 @@ All benchmarks were performed on an i7-8650U Ubuntu Linux laptop with 24GB of me
344
341
  - `react` elements
345
342
  - A mixed object with a combination of all the above types
346
343
 
347
- | | Operations / second |
348
- | -------------------------- | ------------------- |
349
- | **fast-equals** | **153,880** |
350
- | fast-deep-equal | 144,035 |
351
- | react-fast-compare | 130,324 |
352
- | nano-equal | 104,624 |
353
- | **fast-equals (circular)** | **97,610** |
354
- | shallow-equal-fuzzy | 83,946 |
355
- | underscore.isEqual | 47,370 |
356
- | lodash.isEqual | 25,053 |
357
- | deep-eql | 22,146 |
358
- | assert.deepStrictEqual | 532 |
359
- | deep-equal | 209 |
344
+ ```bash
345
+ Testing mixed objects equal...
346
+ ┌─────────┬─────────────────────────────────┬────────────────┐
347
+ (index) Package │ Ops/sec │
348
+ ├─────────┼─────────────────────────────────┼────────────────┤
349
+ │ 0 │ 'fast-equals' │ 1249567.730326 │
350
+ │ 1 │ 'fast-deep-equal' │ 1182463.587514
351
+ │ 2 │ 'react-fast-compare' │ 1152487.319161 │
352
+ │ 3 │ 'shallow-equal-fuzzy' │ 1092360.712389
353
+ │ 4 │ 'fast-equals (circular)' │ 676669.92003 │
354
+ │ 5 │ 'underscore.isEqual' │ 429430.837497 │
355
+ │ 6 │ 'lodash.isEqual' │ 237915.684734 │
356
+ │ 7 │ 'fast-equals (strict)' │ 181386.38032 │
357
+ │ 8 │ 'fast-equals (strict circular)' │ 156779.745875 │
358
+ │ 9 │ 'deep-eql' │ 139155.099209 │
359
+ │ 10 │ 'deep-equal' │ 1026.527229 │
360
+ └─────────┴─────────────────────────────────┴────────────────┘
361
+
362
+ Testing mixed objects not equal...
363
+ ┌─────────┬─────────────────────────────────┬────────────────┐
364
+ │ (index) │ Package │ Ops/sec │
365
+ ├─────────┼─────────────────────────────────┼────────────────┤
366
+ │ 0 │ 'fast-equals' │ 3255824.097237 │
367
+ │ 1 │ 'react-fast-compare' │ 2654721.726058 │
368
+ │ 2 │ 'fast-deep-equal' │ 2582218.974752 │
369
+ │ 3 │ 'fast-equals (circular)' │ 2474303.26566 │
370
+ │ 4 │ 'fast-equals (strict)' │ 1088066.604881 │
371
+ │ 5 │ 'fast-equals (strict circular)' │ 949253.614181 │
372
+ │ 6 │ 'nano-equal' │ 939170.554148 │
373
+ │ 7 │ 'underscore.isEqual' │ 738852.197879 │
374
+ │ 8 │ 'lodash.isEqual' │ 307306.622212 │
375
+ │ 9 │ 'deep-eql' │ 156250.110401 │
376
+ │ 10 │ 'assert.deepStrictEqual' │ 22839.454561 │
377
+ │ 11 │ 'deep-equal' │ 4034.45114 │
378
+ └─────────┴─────────────────────────────────┴────────────────┘
379
+ ```
360
380
 
361
381
  Caveats that impact the benchmark (and accuracy of comparison):
362
382
 
@@ -10,15 +10,6 @@ function combineComparators(comparatorA, comparatorB) {
10
10
  return comparatorA(a, b, state) && comparatorB(a, b, state);
11
11
  };
12
12
  }
13
- /**
14
- * Default equality comparator pass-through, used as the standard `isEqual` creator for
15
- * use inside the built comparator.
16
- */
17
- function createInternalComparator(compare) {
18
- return function (a, b, _indexOrKeyA, _indexOrKeyB, _parentA, _parentB, state) {
19
- return compare(a, b, state);
20
- };
21
- }
22
13
  /**
23
14
  * Wrap the provided `areItemsEqual` method to manage the circular state, allowing
24
15
  * for circular references to be safely included in the comparison without creating
@@ -91,36 +82,42 @@ function areDatesEqual(a, b) {
91
82
  * Whether the `Map`s are equal in value.
92
83
  */
93
84
  function areMapsEqual(a, b, state) {
94
- var isValueEqual = a.size === b.size;
95
- if (isValueEqual && a.size) {
96
- // The use of `forEach()` is to avoid the transpilation cost of `for...of` comparisons, and
97
- // the inability to control the performance of the resulting code. It also avoids excessive
98
- // iteration compared to doing comparisons of `keys()` and `values()`. As a result, though,
99
- // we cannot short-circuit the iterations; bookkeeping must be done to short-circuit the
100
- // equality checks themselves.
101
- var matchedIndices_1 = {};
102
- var indexA_1 = 0;
103
- a.forEach(function (aValue, aKey) {
104
- if (!isValueEqual) {
105
- return;
85
+ if (a.size !== b.size) {
86
+ return false;
87
+ }
88
+ var matchedIndices = {};
89
+ var aIterable = a.entries();
90
+ var index = 0;
91
+ var aResult;
92
+ var bResult;
93
+ while ((aResult = aIterable.next())) {
94
+ if (aResult.done) {
95
+ break;
96
+ }
97
+ var bIterable = b.entries();
98
+ var hasMatch = false;
99
+ var matchIndex = 0;
100
+ while ((bResult = bIterable.next())) {
101
+ if (bResult.done) {
102
+ break;
106
103
  }
107
- var hasMatch = false;
108
- var matchIndexB = 0;
109
- b.forEach(function (bValue, bKey) {
110
- if (!hasMatch &&
111
- !matchedIndices_1[matchIndexB] &&
112
- (hasMatch =
113
- state.equals(aKey, bKey, indexA_1, matchIndexB, a, b, state) &&
114
- state.equals(aValue, bValue, aKey, bKey, a, b, state))) {
115
- matchedIndices_1[matchIndexB] = true;
116
- }
117
- matchIndexB++;
118
- });
119
- indexA_1++;
120
- isValueEqual = hasMatch;
121
- });
104
+ var _a = aResult.value, aKey = _a[0], aValue = _a[1];
105
+ var _b = bResult.value, bKey = _b[0], bValue = _b[1];
106
+ if (!hasMatch &&
107
+ !matchedIndices[matchIndex] &&
108
+ (hasMatch =
109
+ state.equals(aKey, bKey, index, matchIndex, a, b, state) &&
110
+ state.equals(aValue, bValue, aKey, bKey, a, b, state))) {
111
+ matchedIndices[matchIndex] = true;
112
+ }
113
+ matchIndex++;
114
+ }
115
+ if (!hasMatch) {
116
+ return false;
117
+ }
118
+ index++;
122
119
  }
123
- return isValueEqual;
120
+ return true;
124
121
  }
125
122
  /**
126
123
  * Whether the objects are equal in value.
@@ -208,33 +205,40 @@ function areRegExpsEqual(a, b) {
208
205
  * Whether the `Set`s are equal in value.
209
206
  */
210
207
  function areSetsEqual(a, b, state) {
211
- var isValueEqual = a.size === b.size;
212
- if (isValueEqual && a.size) {
213
- // The use of `forEach()` is to avoid the transpilation cost of `for...of` comparisons, and
214
- // the inability to control the performance of the resulting code. It also avoids excessive
215
- // iteration compared to doing comparisons of `keys()` and `values()`. As a result, though,
216
- // we cannot short-circuit the iterations; bookkeeping must be done to short-circuit the
217
- // equality checks themselves.
218
- var matchedIndices_2 = {};
219
- a.forEach(function (aValue, aKey) {
220
- if (!isValueEqual) {
221
- return;
208
+ if (a.size !== b.size) {
209
+ return false;
210
+ }
211
+ var matchedIndices = {};
212
+ var aIterable = a.values();
213
+ var aResult;
214
+ var bResult;
215
+ while ((aResult = aIterable.next())) {
216
+ if (aResult.done) {
217
+ break;
218
+ }
219
+ var bIterable = b.values();
220
+ var hasMatch = false;
221
+ var matchIndex = 0;
222
+ while ((bResult = bIterable.next())) {
223
+ if (bResult.done) {
224
+ break;
222
225
  }
223
- var hasMatch = false;
224
- var matchIndex = 0;
225
- b.forEach(function (bValue, bKey) {
226
- if (!hasMatch &&
227
- !matchedIndices_2[matchIndex] &&
228
- (hasMatch = state.equals(aValue, bValue, aKey, bKey, a, b, state))) {
229
- matchedIndices_2[matchIndex] = true;
230
- }
231
- matchIndex++;
232
- });
233
- isValueEqual = hasMatch;
234
- });
226
+ if (!hasMatch &&
227
+ !matchedIndices[matchIndex] &&
228
+ (hasMatch = state.equals(aResult.value, bResult.value, aResult.value, bResult.value, a, b, state))) {
229
+ matchedIndices[matchIndex] = true;
230
+ }
231
+ matchIndex++;
232
+ }
233
+ if (!hasMatch) {
234
+ return false;
235
+ }
235
236
  }
236
- return isValueEqual;
237
+ return true;
237
238
  }
239
+ /**
240
+ * Whether the TypedArray instances are equal in value.
241
+ */
238
242
  function areTypedArraysEqual(a, b) {
239
243
  var index = a.length;
240
244
  if (b.length !== index) {
@@ -266,7 +270,7 @@ var getTag = Object.prototype.toString.call.bind(Object.prototype.toString);
266
270
  /**
267
271
  * Create a comparator method based on the type-specific equality comparators passed.
268
272
  */
269
- function createComparator(_a) {
273
+ function createEqualityComparator(_a) {
270
274
  var areArraysEqual = _a.areArraysEqual, areDatesEqual = _a.areDatesEqual, areMapsEqual = _a.areMapsEqual, areObjectsEqual = _a.areObjectsEqual, arePrimitiveWrappersEqual = _a.arePrimitiveWrappersEqual, areRegExpsEqual = _a.areRegExpsEqual, areSetsEqual = _a.areSetsEqual, areTypedArraysEqual = _a.areTypedArraysEqual;
271
275
  /**
272
276
  * compare the value of the two objects and return true if they are equivalent in values
@@ -280,9 +284,13 @@ function createComparator(_a) {
280
284
  // of them being equal but not strictly is if they are both `NaN`. Since
281
285
  // `NaN` is uniquely not equal to itself, we can use self-comparison of
282
286
  // both objects, which is faster than `isNaN()`.
283
- if (!a || !b || typeof a !== 'object' || typeof b !== 'object') {
287
+ if (a == null ||
288
+ b == null ||
289
+ typeof a !== 'object' ||
290
+ typeof b !== 'object') {
284
291
  return a !== a && b !== b;
285
292
  }
293
+ var constructor = a.constructor;
286
294
  // Checks are listed in order of commonality of use-case:
287
295
  // 1. Common complex object types (plain object, array)
288
296
  // 2. Common data values (date, regexp)
@@ -291,43 +299,47 @@ function createComparator(_a) {
291
299
  // Inherently this is both subjective and assumptive, however
292
300
  // when reviewing comparable libraries in the wild this order
293
301
  // appears to be generally consistent.
302
+ // Constructors should match, otherwise there is potential for false positives
303
+ // between class and subclass or custom object and POJO.
304
+ if (constructor !== b.constructor) {
305
+ return false;
306
+ }
294
307
  // `isPlainObject` only checks against the object's own realm. Cross-realm
295
308
  // comparisons are rare, and will be handled in the ultimate fallback, so
296
- // we can avoid the `toString.call()` cost unless necessary.
297
- if (a.constructor === Object && b.constructor === Object) {
309
+ // we can avoid capturing the string tag.
310
+ if (constructor === Object) {
298
311
  return areObjectsEqual(a, b, state);
299
312
  }
300
- // In strict mode, constructors should match. We placed this after the plain object check
301
- // because the constructors must match to meet plain object requirements (must both be `Object`
302
- // in the given Realm), so it slightly improves performance on a very common use-case.
303
- if (state.strict && a.constructor !== b.constructor) {
304
- return false;
313
+ // `isArray()` works on subclasses and is cross-realm, so we can avoid capturing
314
+ // the string tag or doing an `instanceof` check.
315
+ if (isArray(a)) {
316
+ return areArraysEqual(a, b, state);
305
317
  }
306
- // `isArray()` works on subclasses and is cross-realm, so we can again avoid
307
- // the `toString.call()` cost unless necessary by just checking if either
308
- // and then both are arrays.
309
- var aArray = isArray(a);
310
- var bArray = isArray(b);
311
- if (aArray || bArray) {
312
- return aArray === bArray && areArraysEqual(a, b, state);
313
- }
314
- // Prioritize the `TypedArray` check because it does not require capturing the tag, which is a
315
- // slower path.
316
- if (isTypedArray) {
317
- aArray = isTypedArray(a);
318
- bArray = isTypedArray(b);
319
- if (aArray || bArray) {
320
- return aArray === bArray && areTypedArraysEqual(a, b, state);
321
- }
318
+ // `isTypedArray()` works on all possible TypedArray classes, so we can avoid
319
+ // capturing the string tag or comparing against all possible constructors.
320
+ if (isTypedArray != null && isTypedArray(a)) {
321
+ return areTypedArraysEqual(a, b, state);
322
322
  }
323
- // Since this is a custom object, use the classic `toString.call()` to get its
324
- // type. This is reasonably performant in modern environments like v8 and
325
- // SpiderMonkey, and allows for cross-realm comparison when other checks like
326
- // `instanceof` do not.
327
- var tag = getTag(a);
328
- if (tag !== getTag(b)) {
329
- return false;
323
+ // Try to fast-path equality checks for other complex object types in the
324
+ // same realm to avoid capturing the string tag. Strict equality is used
325
+ // instead of `instanceof` because it is more performant for the common
326
+ // use-case. If someone is subclassing a native class, it will be handled
327
+ // with the string tag comparison.
328
+ if (constructor === Date) {
329
+ return areDatesEqual(a, b, state);
330
+ }
331
+ if (constructor === RegExp) {
332
+ return areRegExpsEqual(a, b, state);
333
+ }
334
+ if (constructor === Map) {
335
+ return areMapsEqual(a, b, state);
330
336
  }
337
+ if (constructor === Set) {
338
+ return areSetsEqual(a, b, state);
339
+ }
340
+ // Since this is a custom object, capture the string tag to determing its type.
341
+ // This is reasonably performant in modern environments like v8 and SpiderMonkey.
342
+ var tag = getTag(a);
331
343
  if (tag === DATE_TAG) {
332
344
  return areDatesEqual(a, b, state);
333
345
  }
@@ -340,11 +352,9 @@ function createComparator(_a) {
340
352
  if (tag === SET_TAG) {
341
353
  return areSetsEqual(a, b, state);
342
354
  }
343
- // If a simple object tag, then we can prioritize a simple object comparison because
344
- // it is likely a custom class.
345
355
  if (tag === OBJECT_TAG) {
346
- // The exception for value comparison is `Promise`-like contracts. These should be
347
- // treated the same as standard `Promise` objects, which means strict equality, and if
356
+ // The exception for value comparison is custom `Promise`-like class instances. These should
357
+ // be treated the same as standard `Promise` objects, which means strict equality, and if
348
358
  // it reaches this point then that strict equality comparison has already failed.
349
359
  return (typeof a.then !== 'function' &&
350
360
  typeof b.then !== 'function' &&
@@ -377,7 +387,7 @@ function createComparator(_a) {
377
387
  /**
378
388
  * Create the configuration object used for building comparators.
379
389
  */
380
- function createComparatorConfig(_a) {
390
+ function createEqualityComparatorConfig(_a) {
381
391
  var circular = _a.circular, createCustomConfig = _a.createCustomConfig, strict = _a.strict;
382
392
  var config = {
383
393
  areArraysEqual: strict
@@ -396,7 +406,7 @@ function createComparatorConfig(_a) {
396
406
  ? combineComparators(areSetsEqual, areObjectsEqualStrict)
397
407
  : areSetsEqual,
398
408
  areTypedArraysEqual: strict
399
- ? combineComparators(areTypedArraysEqual, areObjectsEqualStrict)
409
+ ? areObjectsEqualStrict
400
410
  : areTypedArraysEqual,
401
411
  };
402
412
  if (createCustomConfig) {
@@ -416,6 +426,51 @@ function createComparatorConfig(_a) {
416
426
  }
417
427
  return config;
418
428
  }
429
+ /**
430
+ * Default equality comparator pass-through, used as the standard `isEqual` creator for
431
+ * use inside the built comparator.
432
+ */
433
+ function createInternalEqualityComparator(compare) {
434
+ return function (a, b, _indexOrKeyA, _indexOrKeyB, _parentA, _parentB, state) {
435
+ return compare(a, b, state);
436
+ };
437
+ }
438
+ /**
439
+ * Create the `isEqual` function used by the consuming application.
440
+ */
441
+ function createIsEqual(_a) {
442
+ var circular = _a.circular, comparator = _a.comparator, createState = _a.createState, equals = _a.equals, strict = _a.strict;
443
+ if (createState) {
444
+ return function isEqual(a, b) {
445
+ var _a = createState(), _b = _a.cache, cache = _b === void 0 ? circular ? new WeakMap() : undefined : _b, meta = _a.meta;
446
+ return comparator(a, b, {
447
+ cache: cache,
448
+ equals: equals,
449
+ meta: meta,
450
+ strict: strict,
451
+ });
452
+ };
453
+ }
454
+ if (circular) {
455
+ return function isEqual(a, b) {
456
+ return comparator(a, b, {
457
+ cache: new WeakMap(),
458
+ equals: equals,
459
+ meta: undefined,
460
+ strict: strict,
461
+ });
462
+ };
463
+ }
464
+ var state = {
465
+ cache: undefined,
466
+ equals: equals,
467
+ meta: undefined,
468
+ strict: strict,
469
+ };
470
+ return function isEqual(a, b) {
471
+ return comparator(a, b, state);
472
+ };
473
+ }
419
474
 
420
475
  /**
421
476
  * Whether the items passed are deeply-equal in value.
@@ -441,29 +496,29 @@ var strictCircularDeepEqual = createCustomEqual({
441
496
  * Whether the items passed are shallowly-equal in value.
442
497
  */
443
498
  var shallowEqual = createCustomEqual({
444
- comparator: sameValueZeroEqual,
499
+ createInternalComparator: function () { return sameValueZeroEqual; },
445
500
  });
446
501
  /**
447
502
  * Whether the items passed are shallowly-equal in value based on strict comparison
448
503
  */
449
504
  var strictShallowEqual = createCustomEqual({
450
- comparator: sameValueZeroEqual,
451
505
  strict: true,
506
+ createInternalComparator: function () { return sameValueZeroEqual; },
452
507
  });
453
508
  /**
454
509
  * Whether the items passed are shallowly-equal in value, including circular references.
455
510
  */
456
511
  var circularShallowEqual = createCustomEqual({
457
- comparator: sameValueZeroEqual,
458
512
  circular: true,
513
+ createInternalComparator: function () { return sameValueZeroEqual; },
459
514
  });
460
515
  /**
461
516
  * Whether the items passed are shallowly-equal in value, including circular references,
462
517
  * based on strict comparison.
463
518
  */
464
519
  var strictCircularShallowEqual = createCustomEqual({
465
- comparator: sameValueZeroEqual,
466
520
  circular: true,
521
+ createInternalComparator: function () { return sameValueZeroEqual; },
467
522
  strict: true,
468
523
  });
469
524
  /**
@@ -476,43 +531,13 @@ var strictCircularShallowEqual = createCustomEqual({
476
531
  */
477
532
  function createCustomEqual(options) {
478
533
  if (options === void 0) { options = {}; }
479
- var circular = options.circular, comparator = options.comparator, createCustomInternalComparator = options.createInternalComparator, createState = options.createState, _a = options.strict, baseStrict = _a === void 0 ? false : _a;
480
- var config = createComparatorConfig(options);
481
- var isEqualCustom = createComparator(config);
482
- var isEqualCustomComparator = comparator ||
483
- (createCustomInternalComparator
484
- ? createCustomInternalComparator(isEqualCustom)
485
- : createInternalComparator(isEqualCustom));
486
- if (createState) {
487
- return function isEqual(a, b, metaOverride) {
488
- var _a = createState(isEqualCustom), _b = _a.cache, cache = _b === void 0 ? circular ? new WeakMap() : undefined : _b, _c = _a.equals, equals = _c === void 0 ? isEqualCustomComparator : _c, meta = _a.meta, _d = _a.strict, strict = _d === void 0 ? baseStrict : _d;
489
- return isEqualCustom(a, b, {
490
- cache: cache,
491
- equals: equals,
492
- meta: metaOverride !== undefined ? metaOverride : meta,
493
- strict: strict,
494
- });
495
- };
496
- }
497
- if (circular) {
498
- return function equals(a, b) {
499
- return isEqualCustom(a, b, {
500
- cache: new WeakMap(),
501
- equals: isEqualCustomComparator,
502
- meta: undefined,
503
- strict: baseStrict,
504
- });
505
- };
506
- }
507
- var state = Object.freeze({
508
- cache: undefined,
509
- equals: isEqualCustomComparator,
510
- meta: undefined,
511
- strict: baseStrict,
512
- });
513
- return function equals(a, b) {
514
- return isEqualCustom(a, b, state);
515
- };
534
+ var _a = options.circular, circular = _a === void 0 ? false : _a, createCustomInternalComparator = options.createInternalComparator, createState = options.createState, _b = options.strict, strict = _b === void 0 ? false : _b;
535
+ var config = createEqualityComparatorConfig(options);
536
+ var comparator = createEqualityComparator(config);
537
+ var equals = createCustomInternalComparator
538
+ ? createCustomInternalComparator(comparator)
539
+ : createInternalEqualityComparator(comparator);
540
+ return createIsEqual({ circular: circular, comparator: comparator, createState: createState, equals: equals, strict: strict });
516
541
  }
517
542
 
518
543
  exports.circularDeepEqual = circularDeepEqual;