Skip to content

Commit 561c76f

Browse files
committed
feat: add Comparator and Memoize options
BREAKING CHANGE: The third argument given to deep-eql is now an options object, which takes the properties `comparator` and/or `memoize`.
1 parent 0fb00d2 commit 561c76f

3 files changed

Lines changed: 285 additions & 31 deletions

File tree

index.js

Lines changed: 122 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
'use strict';
2-
/* globals Symbol: true, Uint8Array: true */
2+
/* globals Symbol: true, Uint8Array: true, WeakMap: true */
33
/*!
44
* deep-eql
55
* Copyright(c) 2013 Jake Luer <jake@alogicalparadox.com>
@@ -13,11 +13,76 @@
1313
var type = require('type-detect');
1414
var objectIs = Object.is || require('object-is'); // eslint-disable-line
1515

16+
function FakeMap() {
17+
this.clear();
18+
}
19+
FakeMap.prototype = {
20+
clear: function clearMap() {
21+
this.keys = [];
22+
this.values = [];
23+
return this;
24+
},
25+
set: function setMap(key, value) {
26+
var index = this.keys.indexOf(key);
27+
if (index >= 0) {
28+
this.values[index] = value;
29+
} else {
30+
this.keys.push(key);
31+
this.values.push(value);
32+
}
33+
return this;
34+
},
35+
get: function getMap(key) {
36+
return this.values[this.keys.indexOf(key)];
37+
},
38+
delete: function deleteMap(key) {
39+
var index = this.keys.indexOf(key);
40+
if (index >= 0) {
41+
this.values = this.values.slice(0, index).concat(this.values.slice(index + 1));
42+
this.keys = this.keys.slice(0, index).concat(this.keys.slice(index + 1));
43+
}
44+
return this;
45+
},
46+
};
47+
48+
var MemoizeMap = null;
49+
if (typeof WeakMap === 'function') {
50+
MemoizeMap = WeakMap;
51+
} else {
52+
MemoizeMap = FakeMap;
53+
}
54+
55+
function memoizeCompare(leftHandOperand, rightHandOperand, memoizeMap) {
56+
var leftHandMap = memoizeMap.get(leftHandOperand);
57+
if (leftHandMap) {
58+
var result = leftHandMap.get(rightHandOperand);
59+
if (typeof result === 'boolean') {
60+
return result;
61+
}
62+
}
63+
return null;
64+
}
65+
66+
function memoizeSet(leftHandOperand, rightHandOperand, memoizeMap, result) {
67+
if (!memoizeMap) {
68+
return;
69+
}
70+
var leftHandMap = memoizeMap.get(leftHandOperand);
71+
if (leftHandMap) {
72+
leftHandMap.set(rightHandOperand, result);
73+
} else {
74+
leftHandMap = new MemoizeMap();
75+
leftHandMap.set(rightHandOperand, result);
76+
memoizeMap.set(leftHandOperand, leftHandMap);
77+
}
78+
}
79+
1680
/*!
1781
* Primary Export
1882
*/
1983

2084
module.exports = deepEqual;
85+
module.exports.MemoizeMap = MemoizeMap;
2186

2287
/**
2388
* Assert deeply nested sameValue equality between two objects of any type.
@@ -29,28 +94,44 @@ module.exports = deepEqual;
2994
* @return {Boolean} equal match
3095
*/
3196

32-
function deepEqual(leftHandOperand, rightHandOperand, comparatorOrMemoize, memoizeObject) {
33-
memoizeObject = memoizeObject || [];
34-
var comparator = comparatorOrMemoize;
35-
if (type(comparatorOrMemoize) !== 'function') {
36-
memoizeObject = comparatorOrMemoize || [];
37-
comparator = false;
97+
function deepEqual(leftHandOperand, rightHandOperand, options) {
98+
options = options || {};
99+
options = {
100+
comparator: options.comparator || objectIs,
101+
memoize: options.memoize || true,
102+
};
103+
if (options.memoize === true) {
104+
options.memoize = new MemoizeMap();
38105
}
39106

40-
if (comparator) {
41-
return comparator(leftHandOperand, rightHandOperand);
107+
var result = objectIs(leftHandOperand, rightHandOperand);
108+
if (result) {
109+
return true;
110+
}
111+
112+
var memoizeResult = memoizeCompare(leftHandOperand, rightHandOperand, options.memoize);
113+
if (typeof memoizeResult === 'boolean') {
114+
return memoizeResult;
42115
}
43116

44-
var sameValue = objectIs(leftHandOperand, rightHandOperand);
45-
if (sameValue) {
117+
if (options.comparator.call(null, leftHandOperand, rightHandOperand)) {
118+
memoizeSet(leftHandOperand, rightHandOperand, options.memoize, true);
119+
memoizeSet(rightHandOperand, leftHandOperand, options.memoize, true);
46120
return true;
47121
}
48122

49123
var leftHandType = type(leftHandOperand);
50124
if (leftHandType !== type(rightHandOperand)) {
125+
memoizeSet(leftHandOperand, rightHandOperand, options.memoize, false);
126+
memoizeSet(rightHandOperand, leftHandOperand, options.memoize, false);
51127
return false;
52128
}
53129

130+
// Temporarily set the operands in the memoize object to prevent blowing the stack
131+
if (typeof leftHandOperand === 'object') {
132+
memoizeSet(leftHandOperand, rightHandOperand, options.memoize, result);
133+
memoizeSet(rightHandOperand, leftHandOperand, options.memoize, result);
134+
}
54135
switch (leftHandType) {
55136
case 'string':
56137
case 'number':
@@ -61,7 +142,7 @@ function deepEqual(leftHandOperand, rightHandOperand, comparatorOrMemoize, memoi
61142
case 'weakmap':
62143
case 'weakset':
63144
case 'error':
64-
return sameValue;
145+
return result;
65146
case 'arguments':
66147
case 'int8array':
67148
case 'uint8array':
@@ -73,22 +154,32 @@ function deepEqual(leftHandOperand, rightHandOperand, comparatorOrMemoize, memoi
73154
case 'float32array':
74155
case 'float64array':
75156
case 'array':
76-
return iterableEqual(leftHandOperand, rightHandOperand, memoizeObject);
157+
result = iterableEqual(leftHandOperand, rightHandOperand, options);
158+
break;
77159
case 'date':
78-
return dateEqual(leftHandOperand, rightHandOperand);
160+
result = dateEqual(leftHandOperand, rightHandOperand);
161+
break;
79162
case 'regexp':
80-
return regexpEqual(leftHandOperand, rightHandOperand);
163+
result = regexpEqual(leftHandOperand, rightHandOperand);
164+
break;
81165
case 'generator':
82-
return generatorEqual(leftHandOperand, rightHandOperand, memoizeObject);
166+
result = generatorEqual(leftHandOperand, rightHandOperand, options);
167+
break;
83168
case 'dataview':
84-
return iterableEqual(new Uint8Array(leftHandOperand.buffer), new Uint8Array(rightHandOperand.buffer));
169+
result = iterableEqual(new Uint8Array(leftHandOperand.buffer), new Uint8Array(rightHandOperand.buffer), options);
170+
break;
85171
case 'arraybuffer':
86-
return iterableEqual(new Uint8Array(leftHandOperand), new Uint8Array(rightHandOperand));
172+
result = iterableEqual(new Uint8Array(leftHandOperand), new Uint8Array(rightHandOperand), options);
173+
break;
87174
case 'set':
88-
return setEqual(leftHandOperand, rightHandOperand, memoizeObject);
175+
result = setEqual(leftHandOperand, rightHandOperand, options);
176+
break;
89177
default:
90-
return objectEqual(leftHandOperand, rightHandOperand, memoizeObject);
178+
result = objectEqual(leftHandOperand, rightHandOperand, options);
91179
}
180+
memoizeSet(leftHandOperand, rightHandOperand, options.memoize, result);
181+
memoizeSet(rightHandOperand, leftHandOperand, options.memoize, result);
182+
return result;
92183
}
93184

94185
/*!
@@ -128,7 +219,7 @@ function regexpEqual(leftHandOperand, rightHandOperand) {
128219
* @return {Boolean} result
129220
*/
130221

131-
function setEqual(leftHandOperand, rightHandOperand) {
222+
function setEqual(leftHandOperand, rightHandOperand, options) {
132223
var leftHandItems = [];
133224
var rightHandItems = [];
134225
leftHandOperand.forEach(function gatherSetEntries(entry) {
@@ -137,7 +228,7 @@ function setEqual(leftHandOperand, rightHandOperand) {
137228
rightHandOperand.forEach(function gatherSetEntries(entry) {
138229
rightHandItems.push(entry);
139230
});
140-
return iterableEqual(leftHandItems.sort(), rightHandItems.sort());
231+
return iterableEqual(leftHandItems.sort(), rightHandItems.sort(), options);
141232
}
142233

143234
/*!
@@ -149,13 +240,13 @@ function setEqual(leftHandOperand, rightHandOperand) {
149240
* @return {Boolean} result
150241
*/
151242

152-
function iterableEqual(leftHandOperand, rightHandOperand, memoizeObject) {
243+
function iterableEqual(leftHandOperand, rightHandOperand, options) {
153244
var length = leftHandOperand.length;
154245
if (length !== rightHandOperand.length) {
155246
return false;
156247
}
157248
for (var i = 0; i < length; i += 1) {
158-
if (deepEqual(leftHandOperand[i], rightHandOperand[i], memoizeObject) === false) {
249+
if (deepEqual(leftHandOperand[i], rightHandOperand[i], options) === false) {
159250
return false;
160251
}
161252
}
@@ -171,8 +262,8 @@ function iterableEqual(leftHandOperand, rightHandOperand, memoizeObject) {
171262
* @return {Boolean} result
172263
*/
173264

174-
function generatorEqual(leftHandOperand, rightHandOperand, memoizeObject) {
175-
return iterableEqual(getGeneratorEntries(leftHandOperand), getGeneratorEntries(rightHandOperand), memoizeObject);
265+
function generatorEqual(leftHandOperand, rightHandOperand, options) {
266+
return iterableEqual(getGeneratorEntries(leftHandOperand), getGeneratorEntries(rightHandOperand), options);
176267
}
177268

178269
/*!
@@ -231,10 +322,10 @@ function getGeneratorEntries(generator) {
231322
*
232323
*
233324
*/
234-
function keysEqual(leftHandOperand, rightHandOperand, keys) {
325+
function keysEqual(leftHandOperand, rightHandOperand, keys, options) {
235326
var length = keys.length;
236327
for (var i = 0; i < length; i += 1) {
237-
if (deepEqual(leftHandOperand[keys[i]], rightHandOperand[keys[i]]) === false) {
328+
if (deepEqual(leftHandOperand[keys[i]], rightHandOperand[keys[i]], options) === false) {
238329
return false;
239330
}
240331
}
@@ -252,7 +343,7 @@ function keysEqual(leftHandOperand, rightHandOperand, keys) {
252343
* @return {Boolean} result
253344
*/
254345

255-
function objectEqual(leftHandOperand, rightHandOperand) {
346+
function objectEqual(leftHandOperand, rightHandOperand, options) {
256347
if (Object.getPrototypeOf(leftHandOperand) !== Object.getPrototypeOf(rightHandOperand)) {
257348
return false;
258349
}
@@ -265,15 +356,15 @@ function objectEqual(leftHandOperand, rightHandOperand) {
265356
if (iterableEqual(leftHandKeys, rightHandKeys) === false) {
266357
return false;
267358
}
268-
return keysEqual(leftHandOperand, rightHandOperand, leftHandKeys);
359+
return keysEqual(leftHandOperand, rightHandOperand, leftHandKeys, options);
269360
}
270361

271362
var leftHandEntries = getIteratorEntries(leftHandOperand);
272363
var rightHandEntries = getIteratorEntries(rightHandOperand);
273364
if (leftHandEntries.length && leftHandEntries.length === rightHandEntries.length) {
274365
leftHandEntries.sort();
275366
rightHandEntries.sort();
276-
return iterableEqual(leftHandEntries, rightHandEntries);
367+
return iterableEqual(leftHandEntries, rightHandEntries, options);
277368
}
278369

279370
if (leftHandKeys.length === 0 &&

0 commit comments

Comments
 (0)