fast-equals 4.0.2 → 5.0.0-beta.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/.babelrc +34 -0
- package/.prettierrc +4 -0
- package/CHANGELOG.md +43 -0
- package/README.md +146 -40
- package/build/rollup/config.base.js +49 -0
- package/build/rollup/config.cjs.js +10 -0
- package/build/rollup/config.esm.js +10 -0
- package/build/rollup/config.min.js +13 -0
- package/build/rollup/config.umd.js +10 -0
- package/build/tsconfig/base.json +32 -0
- package/build/tsconfig/cjs.json +8 -0
- package/build/tsconfig/declarations.json +9 -0
- package/build/tsconfig/esm.json +8 -0
- package/build/tsconfig/min.json +8 -0
- package/build/tsconfig/umd.json +8 -0
- package/build/webpack.config.js +57 -0
- package/dist/cjs/index.cjs +510 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/cjs/types/comparator.d.ts +2 -0
- package/dist/cjs/types/equals.d.ts +46 -0
- package/dist/cjs/types/index.d.ts +56 -0
- package/dist/cjs/types/internalTypes.d.ts +36 -0
- package/dist/cjs/types/utils.d.ts +19 -0
- package/dist/esm/index.mjs +499 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/types/comparator.d.ts +2 -0
- package/dist/esm/types/equals.d.ts +46 -0
- package/dist/esm/types/index.d.ts +56 -0
- package/dist/esm/types/internalTypes.d.ts +36 -0
- package/dist/esm/types/utils.d.ts +19 -0
- package/dist/min/index.js +1 -0
- package/dist/min/types/comparator.d.ts +2 -0
- package/dist/min/types/equals.d.ts +46 -0
- package/dist/min/types/index.d.ts +56 -0
- package/dist/min/types/internalTypes.d.ts +36 -0
- package/dist/min/types/utils.d.ts +19 -0
- package/dist/umd/index.js +516 -0
- package/dist/umd/index.js.map +1 -0
- package/dist/umd/types/comparator.d.ts +2 -0
- package/dist/umd/types/equals.d.ts +46 -0
- package/dist/umd/types/index.d.ts +56 -0
- package/dist/umd/types/internalTypes.d.ts +36 -0
- package/dist/umd/types/utils.d.ts +19 -0
- package/package.json +62 -41
- package/recipes/explicit-property-check.md +4 -6
- package/recipes/legacy-circular-equal-support.md +28 -20
- package/recipes/legacy-regexp-support.md +15 -16
- package/recipes/non-standard-properties.md +35 -23
- package/recipes/using-meta-in-comparison.md +10 -13
- package/src/comparator.ts +50 -47
- package/src/equals.ts +263 -0
- package/src/index.ts +183 -79
- package/src/internalTypes.ts +74 -0
- package/src/utils.ts +42 -49
- package/dist/fast-equals.cjs.js +0 -434
- package/dist/fast-equals.cjs.js.map +0 -1
- package/dist/fast-equals.esm.js +0 -424
- package/dist/fast-equals.esm.js.map +0 -1
- package/dist/fast-equals.js +0 -440
- package/dist/fast-equals.js.map +0 -1
- package/dist/fast-equals.min.js +0 -1
- package/dist/fast-equals.mjs +0 -424
- package/dist/fast-equals.mjs.map +0 -1
- package/index.d.ts +0 -58
- package/recipes/strict-property-descriptor-check.md +0 -42
- package/src/arrays.ts +0 -36
- package/src/dates.ts +0 -12
- package/src/maps.ts +0 -66
- package/src/objects.ts +0 -62
- package/src/regexps.ts +0 -11
- package/src/sets.ts +0 -61
package/src/comparator.ts
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
import { isPlainObject, isPromiseLike, sameValueZeroEqual } from './utils';
|
|
2
|
-
|
|
3
1
|
import type {
|
|
4
|
-
|
|
2
|
+
ComparatorConfig,
|
|
5
3
|
EqualityComparator,
|
|
6
|
-
|
|
4
|
+
State,
|
|
5
|
+
} from './internalTypes';
|
|
7
6
|
|
|
8
7
|
const ARGUMENTS_TAG = '[object Arguments]';
|
|
9
8
|
const BOOLEAN_TAG = '[object Boolean]';
|
|
10
9
|
const DATE_TAG = '[object Date]';
|
|
11
|
-
const REG_EXP_TAG = '[object RegExp]';
|
|
12
10
|
const MAP_TAG = '[object Map]';
|
|
13
11
|
const NUMBER_TAG = '[object Number]';
|
|
14
12
|
const OBJECT_TAG = '[object Object]';
|
|
13
|
+
const REG_EXP_TAG = '[object RegExp]';
|
|
15
14
|
const SET_TAG = '[object Set]';
|
|
16
15
|
const STRING_TAG = '[object String]';
|
|
17
16
|
|
|
18
|
-
const {
|
|
17
|
+
const { isArray } = Array;
|
|
18
|
+
const getTag = Object.prototype.toString.call.bind(
|
|
19
|
+
Object.prototype.toString,
|
|
20
|
+
) as (a: object) => string;
|
|
19
21
|
|
|
20
22
|
export function createComparator<Meta>({
|
|
21
23
|
areArraysEqual,
|
|
22
24
|
areDatesEqual,
|
|
23
25
|
areMapsEqual,
|
|
24
26
|
areObjectsEqual,
|
|
27
|
+
arePrimitiveWrappersEqual,
|
|
25
28
|
areRegExpsEqual,
|
|
26
29
|
areSetsEqual,
|
|
27
|
-
|
|
28
|
-
}: CreateComparatorCreatorOptions<Meta>): EqualityComparator<Meta> {
|
|
29
|
-
const isEqual = createIsNestedEqual(comparator as EqualityComparator<Meta>);
|
|
30
|
-
|
|
30
|
+
}: ComparatorConfig<Meta>): EqualityComparator<Meta> {
|
|
31
31
|
/**
|
|
32
32
|
* compare the value of the two objects and return true if they are equivalent in values
|
|
33
33
|
*/
|
|
34
|
-
function comparator(a: any, b: any,
|
|
34
|
+
return function comparator(a: any, b: any, state: State<Meta>): boolean {
|
|
35
35
|
// If the items are strictly equal, no need to do a value comparison.
|
|
36
36
|
if (a === b) {
|
|
37
37
|
return true;
|
|
@@ -57,79 +57,82 @@ export function createComparator<Meta>({
|
|
|
57
57
|
// `isPlainObject` only checks against the object's own realm. Cross-realm
|
|
58
58
|
// comparisons are rare, and will be handled in the ultimate fallback, so
|
|
59
59
|
// we can avoid the `toString.call()` cost unless necessary.
|
|
60
|
-
if (
|
|
61
|
-
return areObjectsEqual(a, b,
|
|
60
|
+
if (a.constructor === Object && b.constructor === Object) {
|
|
61
|
+
return areObjectsEqual(a, b, state);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
// `isArray()` works on subclasses and is cross-realm, so we can again avoid
|
|
65
65
|
// the `toString.call()` cost unless necessary by just checking if either
|
|
66
66
|
// and then both are arrays.
|
|
67
|
-
const aArray =
|
|
68
|
-
const bArray =
|
|
67
|
+
const aArray = isArray(a);
|
|
68
|
+
const bArray = isArray(b);
|
|
69
69
|
|
|
70
70
|
if (aArray || bArray) {
|
|
71
|
-
return aArray === bArray && areArraysEqual(a, b,
|
|
71
|
+
return aArray === bArray && areArraysEqual(a, b, state);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
// Since this is a custom object, use the classic `toString.call()` to get its
|
|
75
75
|
// type. This is reasonably performant in modern environments like v8 and
|
|
76
76
|
// SpiderMonkey, and allows for cross-realm comparison when other checks like
|
|
77
77
|
// `instanceof` do not.
|
|
78
|
-
const
|
|
78
|
+
const tag = getTag(a);
|
|
79
79
|
|
|
80
|
-
if (
|
|
80
|
+
if (tag !== getTag(b)) {
|
|
81
81
|
return false;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
// or the unary `+` operator.
|
|
87
|
-
return areDatesEqual(a, b, isEqual, meta);
|
|
84
|
+
if (tag === DATE_TAG) {
|
|
85
|
+
return areDatesEqual(a, b, state);
|
|
88
86
|
}
|
|
89
87
|
|
|
90
|
-
if (
|
|
91
|
-
return areRegExpsEqual(a, b,
|
|
88
|
+
if (tag === REG_EXP_TAG) {
|
|
89
|
+
return areRegExpsEqual(a, b, state);
|
|
92
90
|
}
|
|
93
91
|
|
|
94
|
-
if (
|
|
95
|
-
return areMapsEqual(a, b,
|
|
92
|
+
if (tag === MAP_TAG) {
|
|
93
|
+
return areMapsEqual(a, b, state);
|
|
96
94
|
}
|
|
97
95
|
|
|
98
|
-
if (
|
|
99
|
-
return areSetsEqual(a, b,
|
|
96
|
+
if (tag === SET_TAG) {
|
|
97
|
+
return areSetsEqual(a, b, state);
|
|
100
98
|
}
|
|
101
99
|
|
|
102
100
|
// If a simple object tag, then we can prioritize a simple object comparison because
|
|
103
|
-
// it is likely a custom class.
|
|
104
|
-
|
|
105
|
-
if (aTag === OBJECT_TAG || aTag === ARGUMENTS_TAG) {
|
|
101
|
+
// it is likely a custom class.
|
|
102
|
+
if (tag === OBJECT_TAG) {
|
|
106
103
|
// The exception for value comparison is `Promise`-like contracts. These should be
|
|
107
|
-
// treated the same as standard `Promise` objects, which means strict equality
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
104
|
+
// treated the same as standard `Promise` objects, which means strict equality, and if
|
|
105
|
+
// it reaches this point then that strict equality comparison has already failed.
|
|
106
|
+
return (
|
|
107
|
+
typeof a.then !== 'function' &&
|
|
108
|
+
typeof b.then !== 'function' &&
|
|
109
|
+
areObjectsEqual(a, b, state)
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// If an arguments tag, it should be treated as a standard object.
|
|
114
|
+
if (tag === ARGUMENTS_TAG) {
|
|
115
|
+
return areObjectsEqual(a, b, state);
|
|
111
116
|
}
|
|
112
117
|
|
|
113
118
|
// As the penultimate fallback, check if the values passed are primitive wrappers. This
|
|
114
119
|
// is very rare in modern JS, which is why it is deprioritized compared to all other object
|
|
115
120
|
// types.
|
|
116
|
-
if (
|
|
117
|
-
return
|
|
121
|
+
if (tag === BOOLEAN_TAG || tag === NUMBER_TAG || tag === STRING_TAG) {
|
|
122
|
+
return arePrimitiveWrappersEqual(a, b, state);
|
|
118
123
|
}
|
|
119
124
|
|
|
120
|
-
// If not matching any tags that require a specific type of comparison, then
|
|
121
|
-
// equality. This is for a few reasons:
|
|
122
|
-
// -
|
|
125
|
+
// If not matching any tags that require a specific type of comparison, then we hard-code false because
|
|
126
|
+
// the only thing remaining is strict equality, which has already been compared. This is for a few reasons:
|
|
127
|
+
// - Certain types that cannot be introspected (e.g., `WeakMap`). For these types, this is the only
|
|
123
128
|
// comparison that can be made.
|
|
124
129
|
// - For types that can be introspected, but rarely have requirements to be compared
|
|
125
130
|
// (`ArrayBuffer`, `DataView`, etc.), the cost is avoided to prioritize the common
|
|
126
|
-
// use-cases.
|
|
127
|
-
// - For types that can be introspected
|
|
128
|
-
// equality is (`Error`, etc.), the subjective decision
|
|
131
|
+
// use-cases (may be included in a future release, if requested enough).
|
|
132
|
+
// - For types that can be introspected but do not have an objective definition of what
|
|
133
|
+
// equality is (`Error`, etc.), the subjective decision is to be conservative and strictly compare.
|
|
129
134
|
// In all cases, these decisions should be reevaluated based on changes to the language and
|
|
130
135
|
// common development practices.
|
|
131
|
-
return
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return comparator as EqualityComparator<Meta>;
|
|
136
|
+
return false;
|
|
137
|
+
};
|
|
135
138
|
}
|
package/src/equals.ts
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { getStrictProperties, hasOwn, sameValueZeroEqual } from './utils';
|
|
2
|
+
import type { Dictionary, State } from './internalTypes';
|
|
3
|
+
|
|
4
|
+
const OWNER = '_owner';
|
|
5
|
+
|
|
6
|
+
const { getOwnPropertyDescriptor, keys } = Object;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Whether the arrays are equal in value.
|
|
10
|
+
*/
|
|
11
|
+
export function areArraysEqual(a: any[], b: any[], state: State<any>) {
|
|
12
|
+
let index = a.length;
|
|
13
|
+
|
|
14
|
+
if (b.length !== index) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
while (index-- > 0) {
|
|
19
|
+
if (!state.equals(a[index], b[index], index, index, a, b, state)) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Whether the dates passed are equal in value.
|
|
29
|
+
*
|
|
30
|
+
* @NOTE
|
|
31
|
+
* This is a standalone function instead of done inline in the comparator
|
|
32
|
+
* to allow for overrides.
|
|
33
|
+
*/
|
|
34
|
+
export function areDatesEqual(a: Date, b: Date): boolean {
|
|
35
|
+
return sameValueZeroEqual(a.getTime(), b.getTime());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Whether the `Map`s are equal in value.
|
|
40
|
+
*/
|
|
41
|
+
export function areMapsEqual(
|
|
42
|
+
a: Map<any, any>,
|
|
43
|
+
b: Map<any, any>,
|
|
44
|
+
state: State<any>,
|
|
45
|
+
): boolean {
|
|
46
|
+
let isValueEqual = a.size === b.size;
|
|
47
|
+
|
|
48
|
+
if (isValueEqual && a.size) {
|
|
49
|
+
// The use of `forEach()` is to avoid the transpilation cost of `for...of` comparisons, and
|
|
50
|
+
// the inability to control the performance of the resulting code. It also avoids excessive
|
|
51
|
+
// iteration compared to doing comparisons of `keys()` and `values()`. As a result, though,
|
|
52
|
+
// we cannot short-circuit the iterations; bookkeeping must be done to short-circuit the
|
|
53
|
+
// equality checks themselves.
|
|
54
|
+
|
|
55
|
+
const matchedIndices: Record<number, true> = {};
|
|
56
|
+
|
|
57
|
+
let indexA = 0;
|
|
58
|
+
|
|
59
|
+
a.forEach((aValue, aKey) => {
|
|
60
|
+
if (!isValueEqual) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let hasMatch = false;
|
|
65
|
+
let matchIndexB = 0;
|
|
66
|
+
|
|
67
|
+
b.forEach((bValue, bKey) => {
|
|
68
|
+
if (
|
|
69
|
+
!hasMatch &&
|
|
70
|
+
!matchedIndices[matchIndexB] &&
|
|
71
|
+
(hasMatch =
|
|
72
|
+
state.equals(aKey, bKey, indexA, matchIndexB, a, b, state) &&
|
|
73
|
+
state.equals(aValue, bValue, aKey, bKey, a, b, state))
|
|
74
|
+
) {
|
|
75
|
+
matchedIndices[matchIndexB] = true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
matchIndexB++;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
indexA++;
|
|
82
|
+
isValueEqual = hasMatch;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return isValueEqual;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Whether the objects are equal in value.
|
|
91
|
+
*/
|
|
92
|
+
export function areObjectsEqual(
|
|
93
|
+
a: Dictionary,
|
|
94
|
+
b: Dictionary,
|
|
95
|
+
state: State<any>,
|
|
96
|
+
): boolean {
|
|
97
|
+
const properties = keys(a);
|
|
98
|
+
|
|
99
|
+
let index = properties.length;
|
|
100
|
+
|
|
101
|
+
if (keys(b).length !== index) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let property: string | symbol;
|
|
106
|
+
|
|
107
|
+
// Decrementing `while` showed faster results than either incrementing or
|
|
108
|
+
// decrementing `for` loop and than an incrementing `while` loop. Declarative
|
|
109
|
+
// methods like `some` / `every` were not used to avoid incurring the garbage
|
|
110
|
+
// cost of anonymous callbacks.
|
|
111
|
+
while (index-- > 0) {
|
|
112
|
+
property = properties[index]!;
|
|
113
|
+
|
|
114
|
+
if (
|
|
115
|
+
property === OWNER &&
|
|
116
|
+
(a.$$typeof || b.$$typeof) &&
|
|
117
|
+
a.$$typeof !== b.$$typeof
|
|
118
|
+
) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (
|
|
123
|
+
!hasOwn(b, property) ||
|
|
124
|
+
!state.equals(a[property], b[property], property, property, a, b, state)
|
|
125
|
+
) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Whether the objects are equal in value with strict property checking.
|
|
135
|
+
*/
|
|
136
|
+
export function areObjectsEqualStrict(
|
|
137
|
+
a: Dictionary,
|
|
138
|
+
b: Dictionary,
|
|
139
|
+
state: State<any>,
|
|
140
|
+
): boolean {
|
|
141
|
+
const properties = getStrictProperties(a);
|
|
142
|
+
|
|
143
|
+
let index = properties.length;
|
|
144
|
+
|
|
145
|
+
if (getStrictProperties(b).length !== index) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let property: string | symbol;
|
|
150
|
+
let descriptorA: ReturnType<typeof getOwnPropertyDescriptor>;
|
|
151
|
+
let descriptorB: ReturnType<typeof getOwnPropertyDescriptor>;
|
|
152
|
+
|
|
153
|
+
// Decrementing `while` showed faster results than either incrementing or
|
|
154
|
+
// decrementing `for` loop and than an incrementing `while` loop. Declarative
|
|
155
|
+
// methods like `some` / `every` were not used to avoid incurring the garbage
|
|
156
|
+
// cost of anonymous callbacks.
|
|
157
|
+
while (index-- > 0) {
|
|
158
|
+
property = properties[index]!;
|
|
159
|
+
|
|
160
|
+
if (
|
|
161
|
+
property === OWNER &&
|
|
162
|
+
(a.$$typeof || b.$$typeof) &&
|
|
163
|
+
a.$$typeof !== b.$$typeof
|
|
164
|
+
) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (!hasOwn(b, property)) {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (
|
|
173
|
+
!state.equals(a[property], b[property], property, property, a, b, state)
|
|
174
|
+
) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
descriptorA = getOwnPropertyDescriptor(a, property);
|
|
179
|
+
descriptorB = getOwnPropertyDescriptor(b, property);
|
|
180
|
+
|
|
181
|
+
if (
|
|
182
|
+
(descriptorA || descriptorB) &&
|
|
183
|
+
(!descriptorA ||
|
|
184
|
+
!descriptorB ||
|
|
185
|
+
descriptorA.configurable !== descriptorB.configurable ||
|
|
186
|
+
descriptorA.enumerable !== descriptorB.enumerable ||
|
|
187
|
+
descriptorA.writable !== descriptorB.writable)
|
|
188
|
+
) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Whether the primitive wrappers passed are equal in value.
|
|
198
|
+
*
|
|
199
|
+
* @NOTE
|
|
200
|
+
* This is a standalone function instead of done inline in the comparator
|
|
201
|
+
* to allow for overrides.
|
|
202
|
+
*/
|
|
203
|
+
export function arePrimitiveWrappersEqual(a: Date, b: Date): boolean {
|
|
204
|
+
return sameValueZeroEqual(a.valueOf(), b.valueOf());
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Whether the regexps passed are equal in value.
|
|
209
|
+
*
|
|
210
|
+
* @NOTE
|
|
211
|
+
* This is a standalone function instead of done inline in the comparator
|
|
212
|
+
* to allow for overrides. An example of this would be supporting a
|
|
213
|
+
* pre-ES2015 environment where the `flags` property is not available.
|
|
214
|
+
*/
|
|
215
|
+
export function areRegExpsEqual(a: RegExp, b: RegExp): boolean {
|
|
216
|
+
return a.source === b.source && a.flags === b.flags;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Whether the `Set`s are equal in value.
|
|
221
|
+
*/
|
|
222
|
+
export function areSetsEqual(
|
|
223
|
+
a: Set<any>,
|
|
224
|
+
b: Set<any>,
|
|
225
|
+
state: State<any>,
|
|
226
|
+
): boolean {
|
|
227
|
+
let isValueEqual = a.size === b.size;
|
|
228
|
+
|
|
229
|
+
if (isValueEqual && a.size) {
|
|
230
|
+
// The use of `forEach()` is to avoid the transpilation cost of `for...of` comparisons, and
|
|
231
|
+
// the inability to control the performance of the resulting code. It also avoids excessive
|
|
232
|
+
// iteration compared to doing comparisons of `keys()` and `values()`. As a result, though,
|
|
233
|
+
// we cannot short-circuit the iterations; bookkeeping must be done to short-circuit the
|
|
234
|
+
// equality checks themselves.
|
|
235
|
+
|
|
236
|
+
const matchedIndices: Record<number, true> = {};
|
|
237
|
+
|
|
238
|
+
a.forEach((aValue, aKey) => {
|
|
239
|
+
if (!isValueEqual) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
let hasMatch = false;
|
|
244
|
+
let matchIndex = 0;
|
|
245
|
+
|
|
246
|
+
b.forEach((bValue, bKey) => {
|
|
247
|
+
if (
|
|
248
|
+
!hasMatch &&
|
|
249
|
+
!matchedIndices[matchIndex] &&
|
|
250
|
+
(hasMatch = state.equals(aValue, bValue, aKey, bKey, a, b, state))
|
|
251
|
+
) {
|
|
252
|
+
matchedIndices[matchIndex] = true;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
matchIndex++;
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
isValueEqual = hasMatch;
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return isValueEqual;
|
|
263
|
+
}
|