fast-equals 6.0.0-beta.0 → 6.0.0-rc.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.
package/README.md CHANGED
@@ -34,7 +34,7 @@ customize any specific type comparison based on your application's use-cases.
34
34
  - [deepEqual](#deepequal)
35
35
  - [Comparing `Map`s](#comparing-maps)
36
36
  - [shallowEqual](#shallowequal)
37
- - [sameValueZeroEqual](#samevaluezeroequal)
37
+ - [sameValueEqual](#samevalueequal)
38
38
  - [circularDeepEqual](#circulardeepequal)
39
39
  - [circularShallowEqual](#circularshallowequal)
40
40
  - [strictDeepEqual](#strictdeepequal)
@@ -42,7 +42,7 @@ customize any specific type comparison based on your application's use-cases.
42
42
  - [strictCircularDeepEqual](#strictcirculardeepequal)
43
43
  - [strictCircularShallowEqual](#strictcircularshallowequal)
44
44
  - [createCustomEqual](#createcustomequal)
45
- - [unknownTagComparators](#unknowntagcomparators)
45
+ - [getUnsupportedCustomComparator](#getunsupportedcustomcomparator)
46
46
  - [Recipes](#recipes)
47
47
  - [Benchmarks](#benchmarks)
48
48
  - [Development](#development)
@@ -120,14 +120,17 @@ console.log(shallowEqual(objectA, objectB)); // true
120
120
  console.log(shallowEqual(objectA, objectC)); // false
121
121
  ```
122
122
 
123
- ### sameValueZeroEqual
123
+ ### sameValueEqual
124
124
 
125
- Performs a [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) comparison on the two
126
- objects passed and returns a boolean representing the value equivalency of the objects. In simple terms, this means
127
- either strictly equal or both `NaN`.
125
+ Performs a [`SameValue`](http://ecma-international.org/ecma-262/7.0/#sec-samevalue) comparison on the two objects passed
126
+ and returns a boolean representing the value equivalency of the objects. In simple terms, this means:
127
+
128
+ - `+0` and `-0` are not equal
129
+ - `NaN` is equal to `NaN`
130
+ - All other items are based on referential equality (`a === b`)
128
131
 
129
132
  ```ts
130
- import { sameValueZeroEqual } from 'fast-equals';
133
+ import { sameValueEqual } from 'fast-equals';
131
134
 
132
135
  const mainObject = { foo: NaN, bar: 'baz' };
133
136
 
@@ -135,11 +138,15 @@ const objectA = 'baz';
135
138
  const objectB = NaN;
136
139
  const objectC = { foo: NaN, bar: 'baz' };
137
140
 
138
- console.log(sameValueZeroEqual(mainObject.bar, objectA)); // true
139
- console.log(sameValueZeroEqual(mainObject.foo, objectB)); // true
140
- console.log(sameValueZeroEqual(mainObject, objectC)); // false
141
+ console.log(sameValueEqual(mainObject.bar, objectA)); // true
142
+ console.log(sameValueEqual(mainObject.foo, objectB)); // true
143
+ console.log(sameValueEqual(mainObject, objectC)); // false
141
144
  ```
142
145
 
146
+ **NOTE**: In environments that support
147
+ [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is), this is just
148
+ a re-export of that method.
149
+
143
150
  ### circularDeepEqual
144
151
 
145
152
  Performs the same comparison as `deepEqual` but supports circular objects. It is slower than `deepEqual`, so only use if
@@ -298,18 +305,21 @@ interface Cache<Key extends object, Value> {
298
305
  }
299
306
 
300
307
  interface ComparatorConfig<Meta> {
301
- areArraysEqual: TypeEqualityComparator<any[], Meta>;
308
+ areArrayBuffersEcqual: TypeEqualityComparator<ArrayBuffer, Meta>;
309
+ areArraysEcqual: TypeEqualityComparator<any[], Meta>;
310
+ areDataViewsEqual: TypeEqualityComparator<DataView, Meta>;
302
311
  areDatesEqual: TypeEqualityComparator<Date, Meta>;
303
312
  areErrorsEqual: TypeEqualityComparator<Error, Meta>;
304
313
  areFunctionsEqual: TypeEqualityComparator<(...args: any[]) => any, Meta>;
305
314
  areMapsEqual: TypeEqualityComparator<Map<any, any>, Meta>;
315
+ areNumbersEqual: TypeEqualityComparator<number, Meta>;
306
316
  areObjectsEqual: TypeEqualityComparator<Record<string, any>, Meta>;
307
- arePrimitiveWrappersEqual: TypeEqualityComparator<boolean | string | number, Meta>;
317
+ arePrimitiveWrappersEqual: TypeEqualityComparator<Boolean | Number | String, Meta>;
308
318
  areRegExpsEqual: TypeEqualityComparator<RegExp, Meta>;
309
319
  areSetsEqual: TypeEqualityComparator<Set<any>, Meta>;
310
320
  areTypedArraysEqual: TypeEqualityComparator<TypedArray, Meta>;
311
321
  areUrlsEqual: TypeEqualityComparator<URL, Meta>;
312
- unknownTagComparators: Record<string, TypeEqualityComparator<string, any>>;
322
+ getUnsupportedCustomComparator: <Type>(a: Type, b: Type, tag: string) => TypeEqualityComparator<Type, Meta>;
313
323
  }
314
324
 
315
325
  function createCustomEqual<Meta>(options: {
@@ -332,10 +342,11 @@ _**NOTE**: `Map` implementations compare equality for both keys and value. When
332
342
  equality of the keys, the iteration index is provided as both `indexOrKeyA` and `indexOrKeyB` to help use-cases where
333
343
  ordering of keys matters to equality._
334
344
 
335
- #### unknownTagComparators
345
+ #### getUnsupportedCustomComparator
336
346
 
337
347
  If you want to compare objects that have a custom `@@toStringTag`, you can provide a map of the custom tags you want to
338
- support via the `unknownTagComparators` option. See [this recipe]('./recipes/special-objects.md) for an example.
348
+ support via the `getUnsupportedCustomComparator` option. See [this recipe]('./recipes/special-objects.md) for an
349
+ example.
339
350
 
340
351
  #### Recipes
341
352
 
@@ -348,12 +359,12 @@ to the problem you are solving, they can offer guidance of how to structure your
348
359
  - [Comparing non-standard properties](./recipes/non-standard-properties.md)
349
360
  - [Strict property descriptor comparison](./recipes/strict-property-descriptor-check.md)
350
361
  - [Legacy environment support for circualr equal comparators](./recipes/legacy-circular-equal-support.md)
351
- - [Custom `@@toStringTag` support](./recipes/special-objects.md)
362
+ - [Custom comparator support](./recipes/special-objects.md)
352
363
 
353
364
  ## Benchmarks
354
365
 
355
366
  All benchmarks were performed on an i9-11900H Ubuntu Linux 24.04 laptop with 64GB of memory using NodeJS version
356
- `20.17.0`, and are based on averages of running comparisons based deep equality on the following object types:
367
+ `24.11.1`, and are based on averages of running comparisons based deep equality on the following object types:
357
368
 
358
369
  - Primitives (`String`, `Number`, `null`, `undefined`)
359
370
  - `Function`
@@ -365,74 +376,73 @@ All benchmarks were performed on an i9-11900H Ubuntu Linux 24.04 laptop with 64G
365
376
  - A mixed object with a combination of all the above types
366
377
 
367
378
  ```bash
368
- Testing mixed objects equal...
369
379
  ┌────────────────────────────────────────┬────────────────┐
370
380
  │ Name │ Ops / sec │
371
381
  ├────────────────────────────────────────┼────────────────┤
372
- │ fast-equals (passed) │ 1416193.769468
382
+ │ fast-equals (passed) │ 1544237.29413
373
383
  ├────────────────────────────────────────┼────────────────┤
374
- │ fast-deep-equal (passed) │ 1284824.583215
384
+ │ fast-deep-equal (passed) │ 1328583.767745
375
385
  ├────────────────────────────────────────┼────────────────┤
376
- │ react-fast-compare (passed) │ 1246947.505444
386
+ │ react-fast-compare (passed) │ 1301727.296375
377
387
  ├────────────────────────────────────────┼────────────────┤
378
- │ shallow-equal-fuzzy (passed) │ 1238082.379207
388
+ │ shallow-equal-fuzzy (passed) │ 1225981.400919
379
389
  ├────────────────────────────────────────┼────────────────┤
380
- │ nano-equal (failed) │ 946782.33704
390
+ │ nano-equal (failed) │ 969495.538753
381
391
  ├────────────────────────────────────────┼────────────────┤
382
- dequal/lite (passed) 758213.632866
392
+ fast-equals (circular) (passed) 813716.49516
383
393
  ├────────────────────────────────────────┼────────────────┤
384
- │ dequal (passed) 756789.655029
394
+ │ dequal/lite (passed) 780805.627339
385
395
  ├────────────────────────────────────────┼────────────────┤
386
- fast-equals (circular) (passed) 726093.253185
396
+ dequal (passed) 767208.995048
387
397
  ├────────────────────────────────────────┼────────────────┤
388
- │ underscore.isEqual (passed) │ 489748.701783
398
+ │ underscore.isEqual (passed) │ 490695.830468
389
399
  ├────────────────────────────────────────┼────────────────┤
390
- │ assert.deepStrictEqual (passed) │ 453761.890107
400
+ │ assert.deepStrictEqual (passed) │ 471011.425391
391
401
  ├────────────────────────────────────────┼────────────────┤
392
- │ lodash.isEqual (passed) │ 288264.867811
402
+ │ lodash.isEqual (passed) │ 296064.057382
393
403
  ├────────────────────────────────────────┼────────────────┤
394
- │ fast-equals (strict) (passed) │ 217221.619705
404
+ │ fast-equals (strict) (passed) │ 225894.800964
395
405
  ├────────────────────────────────────────┼────────────────┤
396
- │ fast-equals (strict circular) (passed) │ 186916.942934
406
+ │ fast-equals (strict circular) (passed) │ 195657.732354
397
407
  ├────────────────────────────────────────┼────────────────┤
398
- │ deep-eql (passed) │ 162487.877883
408
+ │ deep-eql (passed) │ 162718.102328
399
409
  ├────────────────────────────────────────┼────────────────┤
400
- │ deep-equal (passed) │ 916.680714
410
+ │ deep-equal (passed) │ 954.172311
401
411
  └────────────────────────────────────────┴────────────────┘
402
412
 
403
413
  Testing mixed objects not equal...
404
414
  ┌────────────────────────────────────────┬────────────────┐
405
415
  │ Name │ Ops / sec │
406
416
  ├────────────────────────────────────────┼────────────────┤
407
- │ fast-equals (passed) │ 4687012.640614
417
+ │ fast-equals (passed) │ 5112341.000979
408
418
  ├────────────────────────────────────────┼────────────────┤
409
- │ fast-deep-equal (passed) 3418170.156109
419
+ │ fast-equals (circular) (passed) 3501225.300307
410
420
  ├────────────────────────────────────────┼────────────────┤
411
- react-fast-compare (passed) 3283516.669966
421
+ │ fast-deep-equal (passed) 3471838.735181
412
422
  ├────────────────────────────────────────┼────────────────┤
413
- │ fast-equals (circular) (passed) 3268062.099602
423
+ react-fast-compare (passed) 3439612.908273
414
424
  ├────────────────────────────────────────┼────────────────┤
415
- │ fast-equals (strict) (passed) │ 1747578.66456
425
+ │ fast-equals (strict) (passed) │ 1797319.423491
416
426
  ├────────────────────────────────────────┼────────────────┤
417
- │ fast-equals (strict circular) (passed) │ 1477873.624956
427
+ │ fast-equals (strict circular) (passed) │ 1534168.229167
418
428
  ├────────────────────────────────────────┼────────────────┤
419
- │ dequal/lite (passed) │ 1335397.839502
429
+ │ dequal/lite (passed) │ 1357981.758571
420
430
  ├────────────────────────────────────────┼────────────────┤
421
- │ dequal (passed) │ 1319426.71146
431
+ │ dequal (passed) │ 1328078.173967
422
432
  ├────────────────────────────────────────┼────────────────┤
423
- │ shallow-equal-fuzzy (failed) │ 1237432.986615
433
+ │ shallow-equal-fuzzy (failed) │ 1224747.272118
424
434
  ├────────────────────────────────────────┼────────────────┤
425
- │ nano-equal (passed) │ 1064383.319776
435
+ │ nano-equal (passed) │ 1087373.99615
426
436
  ├────────────────────────────────────────┼────────────────┤
427
- │ underscore.isEqual (passed) │ 920462.516736
437
+ │ underscore.isEqual (passed) │ 927298.592729
428
438
  ├────────────────────────────────────────┼────────────────┤
429
- │ lodash.isEqual (passed) │ 379370.998021
439
+ │ lodash.isEqual (passed) │ 387294.235476
430
440
  ├────────────────────────────────────────┼────────────────┤
431
- │ deep-eql (passed) │ 184111.383127
441
+ │ deep-eql (passed) │ 186028.168827
432
442
  ├────────────────────────────────────────┼────────────────┤
433
- │ assert.deepStrictEqual (passed) │ 20775.59065
443
+ │ assert.deepStrictEqual (passed) │ 21261.312424
434
444
  ├────────────────────────────────────────┼────────────────┤
435
- │ deep-equal (passed) │ 3678.51009
445
+ │ deep-equal (passed) │ 3782.329948
436
446
  └────────────────────────────────────────┴────────────────┘
437
447
  ```
438
448
 
@@ -442,17 +452,17 @@ Caveats that impact the benchmark (and accuracy of comparison):
442
452
  fully supported their comparison
443
453
  - `fast-deep-equal`, `react-fast-compare` and `nano-equal` throw on objects with `null` as prototype
444
454
  (`Object.create(null)`)
445
- - `assert.deepStrictEqual` does not support `NaN` or `SameValueZero` equality for dates
446
- - `deep-eql` does not support `SameValueZero` equality for zero equality (positive and negative zero are not equal)
455
+ - `assert.deepStrictEqual` does not support `NaN` or `SameValue` equality for dates
456
+ - `deep-eql` does not support `SameValue` equality for zero equality (positive and negative zero are not equal)
447
457
  - `deep-equal` does not support `NaN` and does not strictly compare object type, or date / regexp values, nor uses
448
- `SameValueZero` equality for dates
449
- - `fast-deep-equal` does not support `NaN` or `SameValueZero` equality for dates
450
- - `nano-equal` does not strictly compare object property structure, array length, or object type, nor `SameValueZero`
458
+ `SameValue` equality for dates
459
+ - `fast-deep-equal` does not support `NaN` or `SameValue` equality for dates
460
+ - `nano-equal` does not strictly compare object property structure, array length, or object type, nor `SameValue`
451
461
  equality for dates
452
- - `react-fast-compare` does not support `NaN` or `SameValueZero` equality for dates, and does not compare `function`
462
+ - `react-fast-compare` does not support `NaN` or `SameValue` equality for dates, and does not compare `function`
453
463
  equality
454
- - `shallow-equal-fuzzy` does not strictly compare object type or regexp values, nor `SameValueZero` equality for dates
455
- - `underscore.isEqual` does not support `SameValueZero` equality for primitives or dates
464
+ - `shallow-equal-fuzzy` does not strictly compare object type or regexp values, nor `SameValue` equality for dates
465
+ - `underscore.isEqual` does not support `SameValue` equality for primitives or dates
456
466
 
457
467
  All of these have the potential of inflating the respective library's numbers in comparison to `fast-equals`, but it was
458
468
  the closest apples-to-apples comparison I could create of a reasonable sample size. It should be noted that `react`
@@ -35,12 +35,6 @@ function createIsCircular(areItemsEqual) {
35
35
  return result;
36
36
  };
37
37
  }
38
- /**
39
- * Get the `@@toStringTag` of the value, if it exists.
40
- */
41
- function getShortTag(value) {
42
- return value != null ? value[Symbol.toStringTag] : undefined;
43
- }
44
38
  /**
45
39
  * Get the properties to strictly examine, which include both own properties that are
46
40
  * not enumerable and symbol properties.
@@ -57,9 +51,12 @@ Object.hasOwn || ((object, property) => hasOwnProperty.call(object, property));
57
51
  /**
58
52
  * Whether the values passed are strictly equal or both NaN.
59
53
  */
60
- function sameValueZeroEqual(a, b) {
61
- return a === b || (!a && !b && a !== a && b !== b);
62
- }
54
+ const sameValueEqual =
55
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
56
+ Object.is
57
+ || function sameValueEqual(a, b) {
58
+ return a === b ? a !== 0 || 1 / a === 1 / b : a !== a && b !== b;
59
+ };
63
60
 
64
61
  const PREACT_VNODE = '__v';
65
62
  const PREACT_OWNER = '__o';
@@ -97,7 +94,7 @@ function areDataViewsEqual(a, b) {
97
94
  * Whether the dates passed are equal in value.
98
95
  */
99
96
  function areDatesEqual(a, b) {
100
- return sameValueZeroEqual(a.getTime(), b.getTime());
97
+ return sameValueEqual(a.getTime(), b.getTime());
101
98
  }
102
99
  /**
103
100
  * Whether the errors passed are equal in value.
@@ -163,7 +160,7 @@ function areMapsEqual(a, b, state) {
163
160
  /**
164
161
  * Whether the numbers are equal in value.
165
162
  */
166
- const areNumbersEqual = sameValueZeroEqual;
163
+ const areNumbersEqual = sameValueEqual;
167
164
  /**
168
165
  * Whether the objects are equal in value.
169
166
  */
@@ -222,7 +219,7 @@ function areObjectsEqualStrict(a, b, state) {
222
219
  * Whether the primitive wrappers passed are equal in value.
223
220
  */
224
221
  function arePrimitiveWrappersEqual(a, b) {
225
- return sameValueZeroEqual(a.valueOf(), b.valueOf());
222
+ return sameValueEqual(a.valueOf(), b.valueOf());
226
223
  }
227
224
  /**
228
225
  * Whether the regexps passed are equal in value.
@@ -306,39 +303,14 @@ function isPropertyEqual(a, b, state, property) {
306
303
  return hasOwn(b, property) && state.equals(a[property], b[property], property, property, a, b, state);
307
304
  }
308
305
 
309
- const ARRAY_BUFFER_TAG = '[object ArrayBuffer]';
310
- const ARGUMENTS_TAG = '[object Arguments]';
311
- const BOOLEAN_TAG = '[object Boolean]';
312
- const DATA_VIEW_TAG = '[object DataView]';
313
- const DATE_TAG = '[object Date]';
314
- const ERROR_TAG = '[object Error]';
315
- const MAP_TAG = '[object Map]';
316
- const NUMBER_TAG = '[object Number]';
317
- const OBJECT_TAG = '[object Object]';
318
- const REG_EXP_TAG = '[object RegExp]';
319
- const SET_TAG = '[object Set]';
320
- const STRING_TAG = '[object String]';
321
- const TYPED_ARRAY_TAGS = {
322
- '[object Int8Array]': true,
323
- '[object Uint8Array]': true,
324
- '[object Uint8ClampedArray]': true,
325
- '[object Int16Array]': true,
326
- '[object Uint16Array]': true,
327
- '[object Int32Array]': true,
328
- '[object Uint32Array]': true,
329
- '[object Float16Array]': true,
330
- '[object Float32Array]': true,
331
- '[object Float64Array]': true,
332
- '[object BigInt64Array]': true,
333
- '[object BigUint64Array]': true,
334
- };
335
- const URL_TAG = '[object URL]';
336
306
  // eslint-disable-next-line @typescript-eslint/unbound-method
337
307
  const toString = Object.prototype.toString;
338
308
  /**
339
309
  * Create a comparator method based on the type-specific equality comparators passed.
340
310
  */
341
- function createEqualityComparator({ areArrayBuffersEqual, areArraysEqual, areDataViewsEqual, areDatesEqual, areErrorsEqual, areFunctionsEqual, areMapsEqual, areNumbersEqual, areObjectsEqual, arePrimitiveWrappersEqual, areRegExpsEqual, areSetsEqual, areTypedArraysEqual, areUrlsEqual, unknownTagComparators, }) {
311
+ function createEqualityComparator(config) {
312
+ const supportedComparatorMap = createSupportedComparatorMap(config);
313
+ const { areArraysEqual, areDatesEqual, areFunctionsEqual, areMapsEqual, areNumbersEqual, areObjectsEqual, areRegExpsEqual, areSetsEqual, getUnsupportedCustomComparator, } = config;
342
314
  /**
343
315
  * compare the value of the two objects and return true if they are equivalent in values
344
316
  */
@@ -357,7 +329,7 @@ function createEqualityComparator({ areArrayBuffersEqual, areArraysEqual, areDat
357
329
  return false;
358
330
  }
359
331
  if (type !== 'object') {
360
- if (type === 'number') {
332
+ if (type === 'number' || type === 'bigint') {
361
333
  return areNumbersEqual(a, b, state);
362
334
  }
363
335
  if (type === 'function') {
@@ -380,22 +352,17 @@ function createEqualityComparator({ areArrayBuffersEqual, areArraysEqual, areDat
380
352
  if (constructor !== b.constructor) {
381
353
  return false;
382
354
  }
383
- // `isPlainObject` only checks against the object's own realm. Cross-realm
384
- // comparisons are rare, and will be handled in the ultimate fallback, so
385
- // we can avoid capturing the string tag.
386
- if (constructor === Object) {
387
- return areObjectsEqual(a, b, state);
388
- }
389
- // `isArray()` works on subclasses and is cross-realm, so we can avoid capturing
390
- // the string tag or doing an `instanceof` check.
391
- if (Array.isArray(a)) {
392
- return areArraysEqual(a, b, state);
393
- }
394
355
  // Try to fast-path equality checks for other complex object types in the
395
356
  // same realm to avoid capturing the string tag. Strict equality is used
396
357
  // instead of `instanceof` because it is more performant for the common
397
358
  // use-case. If someone is subclassing a native class, it will be handled
398
359
  // with the string tag comparison.
360
+ if (constructor === Object) {
361
+ return areObjectsEqual(a, b, state);
362
+ }
363
+ if (constructor === Array) {
364
+ return areArraysEqual(a, b, state);
365
+ }
399
366
  if (constructor === Date) {
400
367
  return areDatesEqual(a, b, state);
401
368
  }
@@ -408,71 +375,26 @@ function createEqualityComparator({ areArrayBuffersEqual, areArraysEqual, areDat
408
375
  if (constructor === Set) {
409
376
  return areSetsEqual(a, b, state);
410
377
  }
378
+ if (constructor === Promise) {
379
+ // Avoid tag checks for promise values, since we know if they are not referentially equal
380
+ // then they are not equal.
381
+ return false;
382
+ }
383
+ // `isArray()` works on subclasses and is cross-realm, so we can avoid capturing
384
+ // the string tag or doing an `instanceof` in edge cases.
385
+ if (Array.isArray(a)) {
386
+ return areArraysEqual(a, b, state);
387
+ }
411
388
  // Since this is a custom object, capture the string tag to determing its type.
412
389
  // This is reasonably performant in modern environments like v8 and SpiderMonkey.
413
390
  const tag = toString.call(a);
414
- if (tag === DATE_TAG) {
415
- return areDatesEqual(a, b, state);
416
- }
417
- // For RegExp, the properties are not enumerable, and therefore will give false positives if
418
- // tested like a standard object.
419
- if (tag === REG_EXP_TAG) {
420
- return areRegExpsEqual(a, b, state);
421
- }
422
- if (tag === MAP_TAG) {
423
- return areMapsEqual(a, b, state);
424
- }
425
- if (tag === SET_TAG) {
426
- return areSetsEqual(a, b, state);
427
- }
428
- if (tag === OBJECT_TAG) {
429
- // The exception for value comparison is custom `Promise`-like class instances. These should
430
- // be treated the same as standard `Promise` objects, which means strict equality, and if
431
- // it reaches this point then that strict equality comparison has already failed.
432
- return typeof a.then !== 'function' && typeof b.then !== 'function' && areObjectsEqual(a, b, state);
433
- }
434
- // If a URL tag, it should be tested explicitly. Like RegExp, the properties are not
435
- // enumerable, and therefore will give false positives if tested like a standard object.
436
- if (tag === URL_TAG) {
437
- return areUrlsEqual(a, b, state);
438
- }
439
- // If an error tag, it should be tested explicitly. Like RegExp, the properties are not
440
- // enumerable, and therefore will give false positives if tested like a standard object.
441
- if (tag === ERROR_TAG) {
442
- return areErrorsEqual(a, b, state);
443
- }
444
- // If an arguments tag, it should be treated as a standard object.
445
- if (tag === ARGUMENTS_TAG) {
446
- return areObjectsEqual(a, b, state);
391
+ const supportedComparator = supportedComparatorMap[tag];
392
+ if (supportedComparator) {
393
+ return supportedComparator(a, b, state);
447
394
  }
448
- if (TYPED_ARRAY_TAGS[tag]) {
449
- return areTypedArraysEqual(a, b, state);
450
- }
451
- if (tag === ARRAY_BUFFER_TAG) {
452
- return areArrayBuffersEqual(a, b, state);
453
- }
454
- if (tag === DATA_VIEW_TAG) {
455
- return areDataViewsEqual(a, b, state);
456
- }
457
- // As the penultimate fallback, check if the values passed are primitive wrappers. This
458
- // is very rare in modern JS, which is why it is deprioritized compared to all other object
459
- // types.
460
- if (tag === BOOLEAN_TAG || tag === NUMBER_TAG || tag === STRING_TAG) {
461
- return arePrimitiveWrappersEqual(a, b, state);
462
- }
463
- if (unknownTagComparators) {
464
- let unknownTagComparator = unknownTagComparators[tag];
465
- if (!unknownTagComparator) {
466
- const shortTag = getShortTag(a);
467
- if (shortTag) {
468
- unknownTagComparator = unknownTagComparators[shortTag];
469
- }
470
- }
471
- // If the custom config has an unknown tag comparator that matches the captured tag or the
472
- // @@toStringTag, it is the source of truth for whether the values are equal.
473
- if (unknownTagComparator) {
474
- return unknownTagComparator(a, b, state);
475
- }
395
+ const unsupportedCustomComparator = getUnsupportedCustomComparator && getUnsupportedCustomComparator(a, b, tag);
396
+ if (unsupportedCustomComparator) {
397
+ return unsupportedCustomComparator(a, b, state);
476
398
  }
477
399
  // If not matching any tags that require a specific type of comparison, then we hard-code false because
478
400
  // the only thing remaining is strict equality, which has already been compared. This is for a few reasons:
@@ -509,7 +431,7 @@ function createEqualityComparatorConfig({ circular, createCustomConfig, strict,
509
431
  ? combineComparators(areTypedArraysEqual, areObjectsEqualStrict)
510
432
  : areTypedArraysEqual,
511
433
  areUrlsEqual: areUrlsEqual,
512
- unknownTagComparators: undefined,
434
+ getUnsupportedCustomComparator: undefined,
513
435
  };
514
436
  if (createCustomConfig) {
515
437
  config = Object.assign({}, config, createCustomConfig(config));
@@ -572,6 +494,50 @@ function createIsEqual({ circular, comparator, createState, equals, strict }) {
572
494
  return comparator(a, b, state);
573
495
  };
574
496
  }
497
+ /**
498
+ * Create a map of `toString()` values to their respective handlers for `tag`-based lookups.
499
+ */
500
+ function createSupportedComparatorMap({ areArrayBuffersEqual, areArraysEqual, areDataViewsEqual, areDatesEqual, areErrorsEqual, areFunctionsEqual, areMapsEqual, areNumbersEqual, areObjectsEqual, arePrimitiveWrappersEqual, areRegExpsEqual, areSetsEqual, areTypedArraysEqual, areUrlsEqual, }) {
501
+ return {
502
+ '[object Arguments]': areObjectsEqual,
503
+ '[object Array]': areArraysEqual,
504
+ '[object ArrayBuffer]': areArrayBuffersEqual,
505
+ '[object BigInt]': areNumbersEqual,
506
+ '[object BigInt64Array]': areTypedArraysEqual,
507
+ '[object BigUint64Array]': areTypedArraysEqual,
508
+ '[object Boolean]': arePrimitiveWrappersEqual,
509
+ '[object DataView]': areDataViewsEqual,
510
+ '[object Date]': areDatesEqual,
511
+ // If an error tag, it should be tested explicitly. Like RegExp, the properties are not
512
+ // enumerable, and therefore will give false positives if tested like a standard object.
513
+ '[object Error]': areErrorsEqual,
514
+ '[object Float16Array]': areTypedArraysEqual,
515
+ '[object Float32Array]': areTypedArraysEqual,
516
+ '[object Float64Array]': areTypedArraysEqual,
517
+ '[object Function]': areFunctionsEqual,
518
+ '[object GeneratorFunction]': areFunctionsEqual,
519
+ '[object Int8Array]': areTypedArraysEqual,
520
+ '[object Int16Array]': areTypedArraysEqual,
521
+ '[object Int32Array]': areTypedArraysEqual,
522
+ '[object Map]': areMapsEqual,
523
+ '[object Number]': arePrimitiveWrappersEqual,
524
+ '[object Object]': (a, b, state) =>
525
+ // The exception for value comparison is custom `Promise`-like class instances. These should
526
+ // be treated the same as standard `Promise` objects, which means strict equality, and if
527
+ // it reaches this point then that strict equality comparison has already failed.
528
+ typeof a.then !== 'function' && typeof b.then !== 'function' && areObjectsEqual(a, b, state),
529
+ // For RegExp, the properties are not enumerable, and therefore will give false positives if
530
+ // tested like a standard object.
531
+ '[object RegExp]': areRegExpsEqual,
532
+ '[object Set]': areSetsEqual,
533
+ '[object String]': arePrimitiveWrappersEqual,
534
+ '[object URL]': areUrlsEqual,
535
+ '[object Uint8Array]': areTypedArraysEqual,
536
+ '[object Uint8ClampedArray]': areTypedArraysEqual,
537
+ '[object Uint16Array]': areTypedArraysEqual,
538
+ '[object Uint32Array]': areTypedArraysEqual,
539
+ };
540
+ }
575
541
 
576
542
  /**
577
543
  * Whether the items passed are deeply-equal in value.
@@ -597,21 +563,21 @@ const strictCircularDeepEqual = createCustomEqual({
597
563
  * Whether the items passed are shallowly-equal in value.
598
564
  */
599
565
  const shallowEqual = createCustomEqual({
600
- createInternalComparator: () => sameValueZeroEqual,
566
+ createInternalComparator: () => sameValueEqual,
601
567
  });
602
568
  /**
603
569
  * Whether the items passed are shallowly-equal in value based on strict comparison
604
570
  */
605
571
  const strictShallowEqual = createCustomEqual({
606
572
  strict: true,
607
- createInternalComparator: () => sameValueZeroEqual,
573
+ createInternalComparator: () => sameValueEqual,
608
574
  });
609
575
  /**
610
576
  * Whether the items passed are shallowly-equal in value, including circular references.
611
577
  */
612
578
  const circularShallowEqual = createCustomEqual({
613
579
  circular: true,
614
- createInternalComparator: () => sameValueZeroEqual,
580
+ createInternalComparator: () => sameValueEqual,
615
581
  });
616
582
  /**
617
583
  * Whether the items passed are shallowly-equal in value, including circular references,
@@ -619,7 +585,7 @@ const circularShallowEqual = createCustomEqual({
619
585
  */
620
586
  const strictCircularShallowEqual = createCustomEqual({
621
587
  circular: true,
622
- createInternalComparator: () => sameValueZeroEqual,
588
+ createInternalComparator: () => sameValueEqual,
623
589
  strict: true,
624
590
  });
625
591
  /**
@@ -644,7 +610,7 @@ exports.circularDeepEqual = circularDeepEqual;
644
610
  exports.circularShallowEqual = circularShallowEqual;
645
611
  exports.createCustomEqual = createCustomEqual;
646
612
  exports.deepEqual = deepEqual;
647
- exports.sameValueZeroEqual = sameValueZeroEqual;
613
+ exports.sameValueEqual = sameValueEqual;
648
614
  exports.shallowEqual = shallowEqual;
649
615
  exports.strictCircularDeepEqual = strictCircularDeepEqual;
650
616
  exports.strictCircularShallowEqual = strictCircularShallowEqual;