@@ -59,7 +59,8 @@ if (typeof WeakMap === 'function') {
5959 * @returns {Boolean|null } result
6060*/
6161function memoizeCompare ( leftHandOperand , rightHandOperand , memoizeMap ) {
62- if ( ! memoizeMap ) {
62+ // Technically, WeakMap keys can *only* be objects, not primitives.
63+ if ( ! memoizeMap || isPrimitive ( leftHandOperand ) || isPrimitive ( rightHandOperand ) ) {
6364 return null ;
6465 }
6566 var leftHandMap = memoizeMap . get ( leftHandOperand ) ;
@@ -81,7 +82,8 @@ function memoizeCompare(leftHandOperand, rightHandOperand, memoizeMap) {
8182 * @param {Boolean } result
8283*/
8384function memoizeSet ( leftHandOperand , rightHandOperand , memoizeMap , result ) {
84- if ( ! memoizeMap || typeof leftHandOperand !== 'object' || typeof rightHandOperand !== 'object' ) {
85+ // Technically, WeakMap keys can *only* be objects, not primitives.
86+ if ( ! memoizeMap || isPrimitive ( leftHandOperand ) || isPrimitive ( rightHandOperand ) ) {
8587 return ;
8688 }
8789 var leftHandMap = memoizeMap . get ( leftHandOperand ) ;
@@ -114,6 +116,27 @@ module.exports.MemoizeMap = MemoizeMap;
114116 * @return {Boolean } equal match
115117 */
116118function deepEqual ( leftHandOperand , rightHandOperand , options ) {
119+ // If we have a comparator, we can't assume anything; so bail to its check first.
120+ if ( options && options . comparator ) {
121+ return extensiveDeepEqual ( leftHandOperand , rightHandOperand , options ) ;
122+ }
123+
124+ var simpleResult = simpleEqual ( leftHandOperand , rightHandOperand ) ;
125+ if ( simpleResult !== null ) {
126+ return simpleResult ;
127+ }
128+
129+ // Deeper comparisons are pushed through to a larger function
130+ return extensiveDeepEqual ( leftHandOperand , rightHandOperand , options ) ;
131+ }
132+
133+ /**
134+ * Many comparisons can be canceled out early via simple equality or primitive checks.
135+ * @param {Mixed } leftHandOperand
136+ * @param {Mixed } rightHandOperand
137+ * @return {Boolean|null } equal match
138+ */
139+ function simpleEqual ( leftHandOperand , rightHandOperand ) {
117140 // Equal references (except for Numbers) can be returned early
118141 if ( leftHandOperand === rightHandOperand ) {
119142 // Handle +-0 cases
@@ -128,17 +151,13 @@ function deepEqual(leftHandOperand, rightHandOperand, options) {
128151 return true ;
129152 }
130153
131- // Primitives/null/undefined can be checked for referential equality; returning early
132- if (
133- typeof leftHandOperand !== 'object' ||
134- leftHandOperand === null ||
135- leftHandOperand === undefined // eslint-disable-line no-undefined
136- ) {
137- return leftHandOperand === rightHandOperand ;
154+ // Anything that is not an 'object', i.e. symbols, functions, booleans, numbers,
155+ // strings, and undefined, can be compared by reference.
156+ if ( isPrimitive ( leftHandOperand ) || isPrimitive ( rightHandOperand ) ) {
157+ // Easy out b/c it would have passed the first equality check
158+ return false ;
138159 }
139-
140- // Deeper comparisons are pushed through to a larger function
141- return extensiveDeepEqual ( leftHandOperand , rightHandOperand , options ) ;
160+ return null ;
142161}
143162
144163/*!
@@ -158,32 +177,44 @@ function extensiveDeepEqual(leftHandOperand, rightHandOperand, options) {
158177 options . memoize = options . memoize === false ? false : options . memoize || new MemoizeMap ( ) ;
159178 var comparator = options && options . comparator ;
160179
161- var memoizeResult = memoizeCompare ( leftHandOperand , rightHandOperand , options . memoize ) ;
162- if ( memoizeResult !== null ) {
163- return memoizeResult ;
180+ // Check if a memoized result exists.
181+ var memoizeResultLeft = memoizeCompare ( leftHandOperand , rightHandOperand , options . memoize ) ;
182+ if ( memoizeResultLeft !== null ) {
183+ return memoizeResultLeft ;
184+ }
185+ var memoizeResultRight = memoizeCompare ( rightHandOperand , leftHandOperand , options . memoize ) ;
186+ if ( memoizeResultRight !== null ) {
187+ return memoizeResultRight ;
164188 }
165189
166- var comparatorResult = comparator && comparator ( leftHandOperand , rightHandOperand ) ;
167- if ( comparatorResult === false || comparatorResult === true ) {
168- memoizeSet ( leftHandOperand , rightHandOperand , options . memoize , comparatorResult ) ;
169- memoizeSet ( rightHandOperand , leftHandOperand , options . memoize , comparatorResult ) ;
170- return comparatorResult ;
190+ // If a comparator is present, use it.
191+ if ( comparator ) {
192+ var comparatorResult = comparator ( leftHandOperand , rightHandOperand ) ;
193+ // Comparators may return null, in which case we want to go back to default behavior.
194+ if ( comparatorResult === false || comparatorResult === true ) {
195+ memoizeSet ( leftHandOperand , rightHandOperand , options . memoize , comparatorResult ) ;
196+ return comparatorResult ;
197+ }
198+ // To allow comparators to override *any* behavior, we ran them first. Since it didn't decide
199+ // what to do, we need to make sure to return the basic tests first before we move on.
200+ var simpleResult = simpleEqual ( leftHandOperand , rightHandOperand ) ;
201+ if ( simpleResult !== null ) {
202+ // Don't memoize this, it takes longer to set/retrieve than to just compare.
203+ return simpleResult ;
204+ }
171205 }
172206
173207 var leftHandType = type ( leftHandOperand ) ;
174208 if ( leftHandType !== type ( rightHandOperand ) ) {
175209 memoizeSet ( leftHandOperand , rightHandOperand , options . memoize , false ) ;
176- memoizeSet ( rightHandOperand , leftHandOperand , options . memoize , false ) ;
177210 return false ;
178211 }
179212
180213 // Temporarily set the operands in the memoize object to prevent blowing the stack
181214 memoizeSet ( leftHandOperand , rightHandOperand , options . memoize , true ) ;
182- memoizeSet ( rightHandOperand , leftHandOperand , options . memoize , true ) ;
183215
184216 var result = extensiveDeepEqualByType ( leftHandOperand , rightHandOperand , leftHandType , options ) ;
185217 memoizeSet ( leftHandOperand , rightHandOperand , options . memoize , result ) ;
186- memoizeSet ( rightHandOperand , leftHandOperand , options . memoize , result ) ;
187218 return result ;
188219}
189220
@@ -433,3 +464,16 @@ function objectEqual(leftHandOperand, rightHandOperand, options) {
433464
434465 return false ;
435466}
467+
468+ /*!
469+ * Returns true if the argument is a primitive.
470+ *
471+ * This intentionally returns true for all objects that can be compared by reference,
472+ * including functions and symbols.
473+ *
474+ * @param {Mixed } value
475+ * @return {Boolean } result
476+ */
477+ function isPrimitive ( value ) {
478+ return value === null || typeof value !== 'object' ;
479+ }
0 commit comments