Skip to content

Commit 9ab5062

Browse files
izelnakriKrinkle
authored andcommitted
Core: Make equiv internal pairs state passed in
Ref qunitjs#1701
1 parent 3b00e36 commit 9ab5062

File tree

1 file changed

+19
-34
lines changed

1 file changed

+19
-34
lines changed

src/equiv.js

Lines changed: 19 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,6 @@ import { StringSet } from './globals';
33

44
const BOXABLE_TYPES = new StringSet(['boolean', 'number', 'string']);
55

6-
// Memory for previously seen containers (object, array, map, set).
7-
// Used for recursion detection, and to avoid repeated comparison.
8-
//
9-
// Elements are { a: val, b: val }.
10-
let memory = [];
11-
126
function useStrictEquality (a, b) {
137
return a === b;
148
}
@@ -69,14 +63,14 @@ const objTypeCallbacks = {
6963
// identical reference only
7064
function: useStrictEquality,
7165

72-
array (a, b) {
66+
array (a, b, memory) {
7367
if (a.length !== b.length) {
7468
// Safe and faster
7569
return false;
7670
}
7771

7872
for (let i = 0; i < a.length; i++) {
79-
if (!typeEquiv(a[i], b[i])) {
73+
if (!typeEquiv(a[i], b[i], memory)) {
8074
return false;
8175
}
8276
}
@@ -116,14 +110,11 @@ const objTypeCallbacks = {
116110
return;
117111
}
118112

119-
// Swap out the global memory, as nested typeEquiv() would clobber it
120-
const originalMemory = memory;
121-
memory = [];
113+
// Don't pass memory, as this nested typeEquiv() must operate
114+
// independently and would otherwise clobber it
122115
if (typeEquiv(bVal, aVal)) {
123116
innerEq = true;
124117
}
125-
// Restore
126-
memory = originalMemory;
127118
});
128119

129120
if (!innerEq) {
@@ -168,14 +159,11 @@ const objTypeCallbacks = {
168159
return;
169160
}
170161

171-
// Swap out the global memory, as nested typeEquiv() would clobber it
172-
const originalMemory = memory;
173-
memory = [];
174-
if (objTypeCallbacks.array([bVal, bKey], [aVal, aKey])) {
162+
// Don't memory main memory, as nested typeEquiv() would clobber it
163+
const localMemory = [];
164+
if (objTypeCallbacks.array([bVal, bKey], [aVal, aKey], localMemory)) {
175165
innerEq = true;
176166
}
177-
// Restore
178-
memory = originalMemory;
179167
});
180168

181169
if (!innerEq) {
@@ -200,7 +188,7 @@ const entryTypeCallbacks = {
200188
symbol: useStrictEquality,
201189

202190
function: useStrictEquality,
203-
object (a, b) {
191+
object (a, b, memory) {
204192
// Handle memory (skip recursion)
205193
if (memory.some((pair) => pair.a === a && pair.b === b)) {
206194
return true;
@@ -212,7 +200,7 @@ const entryTypeCallbacks = {
212200
if (aObjType !== 'object' || bObjType !== 'object') {
213201
// Handle literal `null`
214202
// Handle: Array, Map/Set, Date, Regxp/Function, boxed primitives
215-
return aObjType === bObjType && objTypeCallbacks[aObjType](a, b);
203+
return aObjType === bObjType && objTypeCallbacks[aObjType](a, b, memory);
216204
}
217205

218206
// NOTE: Literal null must not make it here as it would throw
@@ -238,7 +226,7 @@ const entryTypeCallbacks = {
238226
) {
239227
continue;
240228
}
241-
if (!typeEquiv(a[i], b[i])) {
229+
if (!typeEquiv(a[i], b[i], memory)) {
242230
return false;
243231
}
244232
}
@@ -248,11 +236,15 @@ const entryTypeCallbacks = {
248236
bProperties.push(i);
249237
}
250238

251-
return objTypeCallbacks.array(aProperties.sort(), bProperties.sort());
239+
return objTypeCallbacks.array(aProperties.sort(), bProperties.sort(), memory);
252240
}
253241
};
254242

255-
function typeEquiv (a, b) {
243+
// Memory for previously seen containers (object, array, map, set).
244+
// Used for recursion detection, and to avoid repeated comparison.
245+
//
246+
// Elements are { a: val, b: val }.
247+
function typeEquiv (a, b, memory = []) {
256248
// Optimization: Only perform type-specific comparison when pairs are not strictly equal.
257249
if (a === b) {
258250
return true;
@@ -267,14 +259,7 @@ function typeEquiv (a, b) {
267259
(bType === 'object' && BOXABLE_TYPES.has(objectType(b)) ? b.valueOf() : b);
268260
}
269261

270-
return entryTypeCallbacks[aType](a, b);
271-
}
272-
273-
function innerEquiv (a, b) {
274-
const res = typeEquiv(a, b);
275-
// Release any retained objects and reset recursion detection for next call
276-
memory = [];
277-
return res;
262+
return entryTypeCallbacks[aType](a, b, memory);
278263
}
279264

280265
/**
@@ -285,14 +270,14 @@ function innerEquiv (a, b) {
285270
*/
286271
export default function equiv (a, b) {
287272
if (arguments.length === 2) {
288-
return (a === b) || innerEquiv(a, b);
273+
return (a === b) || typeEquiv(a, b);
289274
}
290275

291276
// Given 0 or 1 arguments, just return true (nothing to compare).
292277
// Given (A,B,C,D) compare C,D then B,C then A,B.
293278
let i = arguments.length - 1;
294279
while (i > 0) {
295-
if (!innerEquiv(arguments[i - 1], arguments[i])) {
280+
if (!typeEquiv(arguments[i - 1], arguments[i])) {
296281
return false;
297282
}
298283
i--;

0 commit comments

Comments
 (0)