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 +66 -56
- package/dist/cjs/index.cjs +85 -119
- package/dist/cjs/index.d.cts +174 -32
- package/dist/es/index.d.mts +174 -32
- package/dist/es/index.mjs +85 -119
- package/index.d.ts +130 -182
- package/package.json +18 -12
- package/dist/cjs/comparator.d.cts +0 -60
- package/dist/cjs/equals.d.cts +0 -62
- package/dist/cjs/internalTypes.d.cts +0 -178
- package/dist/cjs/utils.d.cts +0 -33
- package/dist/es/comparator.d.mts +0 -60
- package/dist/es/equals.d.mts +0 -62
- package/dist/es/internalTypes.d.mts +0 -178
- package/dist/es/utils.d.mts +0 -33
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
|
-
- [
|
|
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
|
-
- [
|
|
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
|
-
###
|
|
123
|
+
### sameValueEqual
|
|
124
124
|
|
|
125
|
-
Performs a [`
|
|
126
|
-
|
|
127
|
-
|
|
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 {
|
|
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(
|
|
139
|
-
console.log(
|
|
140
|
-
console.log(
|
|
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
|
-
|
|
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<
|
|
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
|
-
|
|
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
|
-
####
|
|
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 `
|
|
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
|
|
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
|
-
`
|
|
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) │
|
|
382
|
+
│ fast-equals (passed) │ 1544237.29413 │
|
|
373
383
|
├────────────────────────────────────────┼────────────────┤
|
|
374
|
-
│ fast-deep-equal (passed) │
|
|
384
|
+
│ fast-deep-equal (passed) │ 1328583.767745 │
|
|
375
385
|
├────────────────────────────────────────┼────────────────┤
|
|
376
|
-
│ react-fast-compare (passed) │
|
|
386
|
+
│ react-fast-compare (passed) │ 1301727.296375 │
|
|
377
387
|
├────────────────────────────────────────┼────────────────┤
|
|
378
|
-
│ shallow-equal-fuzzy (passed) │
|
|
388
|
+
│ shallow-equal-fuzzy (passed) │ 1225981.400919 │
|
|
379
389
|
├────────────────────────────────────────┼────────────────┤
|
|
380
|
-
│ nano-equal (failed) │
|
|
390
|
+
│ nano-equal (failed) │ 969495.538753 │
|
|
381
391
|
├────────────────────────────────────────┼────────────────┤
|
|
382
|
-
│
|
|
392
|
+
│ fast-equals (circular) (passed) │ 813716.49516 │
|
|
383
393
|
├────────────────────────────────────────┼────────────────┤
|
|
384
|
-
│ dequal (passed)
|
|
394
|
+
│ dequal/lite (passed) │ 780805.627339 │
|
|
385
395
|
├────────────────────────────────────────┼────────────────┤
|
|
386
|
-
│
|
|
396
|
+
│ dequal (passed) │ 767208.995048 │
|
|
387
397
|
├────────────────────────────────────────┼────────────────┤
|
|
388
|
-
│ underscore.isEqual (passed) │
|
|
398
|
+
│ underscore.isEqual (passed) │ 490695.830468 │
|
|
389
399
|
├────────────────────────────────────────┼────────────────┤
|
|
390
|
-
│ assert.deepStrictEqual (passed) │
|
|
400
|
+
│ assert.deepStrictEqual (passed) │ 471011.425391 │
|
|
391
401
|
├────────────────────────────────────────┼────────────────┤
|
|
392
|
-
│ lodash.isEqual (passed) │
|
|
402
|
+
│ lodash.isEqual (passed) │ 296064.057382 │
|
|
393
403
|
├────────────────────────────────────────┼────────────────┤
|
|
394
|
-
│ fast-equals (strict) (passed) │
|
|
404
|
+
│ fast-equals (strict) (passed) │ 225894.800964 │
|
|
395
405
|
├────────────────────────────────────────┼────────────────┤
|
|
396
|
-
│ fast-equals (strict circular) (passed) │
|
|
406
|
+
│ fast-equals (strict circular) (passed) │ 195657.732354 │
|
|
397
407
|
├────────────────────────────────────────┼────────────────┤
|
|
398
|
-
│ deep-eql (passed) │
|
|
408
|
+
│ deep-eql (passed) │ 162718.102328 │
|
|
399
409
|
├────────────────────────────────────────┼────────────────┤
|
|
400
|
-
│ deep-equal (passed) │
|
|
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) │
|
|
417
|
+
│ fast-equals (passed) │ 5112341.000979 │
|
|
408
418
|
├────────────────────────────────────────┼────────────────┤
|
|
409
|
-
│ fast-
|
|
419
|
+
│ fast-equals (circular) (passed) │ 3501225.300307 │
|
|
410
420
|
├────────────────────────────────────────┼────────────────┤
|
|
411
|
-
│
|
|
421
|
+
│ fast-deep-equal (passed) │ 3471838.735181 │
|
|
412
422
|
├────────────────────────────────────────┼────────────────┤
|
|
413
|
-
│ fast-
|
|
423
|
+
│ react-fast-compare (passed) │ 3439612.908273 │
|
|
414
424
|
├────────────────────────────────────────┼────────────────┤
|
|
415
|
-
│ fast-equals (strict) (passed) │
|
|
425
|
+
│ fast-equals (strict) (passed) │ 1797319.423491 │
|
|
416
426
|
├────────────────────────────────────────┼────────────────┤
|
|
417
|
-
│ fast-equals (strict circular) (passed) │
|
|
427
|
+
│ fast-equals (strict circular) (passed) │ 1534168.229167 │
|
|
418
428
|
├────────────────────────────────────────┼────────────────┤
|
|
419
|
-
│ dequal/lite (passed) │
|
|
429
|
+
│ dequal/lite (passed) │ 1357981.758571 │
|
|
420
430
|
├────────────────────────────────────────┼────────────────┤
|
|
421
|
-
│ dequal (passed) │
|
|
431
|
+
│ dequal (passed) │ 1328078.173967 │
|
|
422
432
|
├────────────────────────────────────────┼────────────────┤
|
|
423
|
-
│ shallow-equal-fuzzy (failed) │
|
|
433
|
+
│ shallow-equal-fuzzy (failed) │ 1224747.272118 │
|
|
424
434
|
├────────────────────────────────────────┼────────────────┤
|
|
425
|
-
│ nano-equal (passed) │
|
|
435
|
+
│ nano-equal (passed) │ 1087373.99615 │
|
|
426
436
|
├────────────────────────────────────────┼────────────────┤
|
|
427
|
-
│ underscore.isEqual (passed) │
|
|
437
|
+
│ underscore.isEqual (passed) │ 927298.592729 │
|
|
428
438
|
├────────────────────────────────────────┼────────────────┤
|
|
429
|
-
│ lodash.isEqual (passed) │
|
|
439
|
+
│ lodash.isEqual (passed) │ 387294.235476 │
|
|
430
440
|
├────────────────────────────────────────┼────────────────┤
|
|
431
|
-
│ deep-eql (passed) │
|
|
441
|
+
│ deep-eql (passed) │ 186028.168827 │
|
|
432
442
|
├────────────────────────────────────────┼────────────────┤
|
|
433
|
-
│ assert.deepStrictEqual (passed) │
|
|
443
|
+
│ assert.deepStrictEqual (passed) │ 21261.312424 │
|
|
434
444
|
├────────────────────────────────────────┼────────────────┤
|
|
435
|
-
│ deep-equal (passed) │
|
|
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 `
|
|
446
|
-
- `deep-eql` does not support `
|
|
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
|
-
`
|
|
449
|
-
- `fast-deep-equal` does not support `NaN` or `
|
|
450
|
-
- `nano-equal` does not strictly compare object property structure, array length, or object type, nor `
|
|
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 `
|
|
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 `
|
|
455
|
-
- `underscore.isEqual` does not support `
|
|
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`
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -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
|
-
|
|
61
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
-
|
|
415
|
-
|
|
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
|
-
|
|
449
|
-
|
|
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
|
-
|
|
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: () =>
|
|
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: () =>
|
|
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: () =>
|
|
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: () =>
|
|
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.
|
|
613
|
+
exports.sameValueEqual = sameValueEqual;
|
|
648
614
|
exports.shallowEqual = shallowEqual;
|
|
649
615
|
exports.strictCircularDeepEqual = strictCircularDeepEqual;
|
|
650
616
|
exports.strictCircularShallowEqual = strictCircularShallowEqual;
|