@@ -12,127 +12,151 @@ import toBufferView from './_toBufferView.js';
1212// We use this string twice, so give it a name for minification.
1313var tagDataView = '[object DataView]' ;
1414
15- // Internal recursive comparison function for `_.isEqual`.
16- function eq ( a , b , aStack , bStack ) {
17- // Identical objects are equal. `0 === -0`, but they aren't identical.
18- // See the [Harmony `egal` proposal](https://wiki.ecmascript.org/doku.php?id=harmony:egal).
19- if ( a === b ) return a !== 0 || 1 / a === 1 / b ;
20- // `null` or `undefined` only equal to itself (strict comparison).
21- if ( a == null || b == null ) return false ;
22- // `NaN`s are equivalent, but non-reflexive.
23- if ( a !== a ) return b !== b ;
24- // Exhaust primitive checks
25- var type = typeof a ;
26- if ( type !== 'function' && type !== 'object' && typeof b != 'object' ) return false ;
27- return deepEq ( a , b , aStack , bStack ) ;
28- }
15+ // Perform a deep comparison to check if two objects are equal.
16+ export default function isEqual ( a , b ) {
17+ // Keep track of which pairs of values need to be compared. We will be
18+ // trampolining on this stack instead of using function recursion.
19+ var todo = [ { a : a , b : b } ] ;
20+ // Initializing stacks of traversed objects for cycle detection.
21+ var aStack = [ ] , bStack = [ ] ;
2922
30- // Internal recursive comparison function for `_.isEqual`.
31- function deepEq ( a , b , aStack , bStack ) {
32- // Unwrap any wrapped objects.
33- if ( a instanceof _ ) a = a . _wrapped ;
34- if ( b instanceof _ ) b = b . _wrapped ;
35- // Compare `[[Class]]` names.
36- var className = toString . call ( a ) ;
37- if ( className !== toString . call ( b ) ) return false ;
38- // Work around a bug in IE 10 - Edge 13.
39- if ( hasDataViewBug && className == '[object Object]' && isDataView ( a ) ) {
40- if ( ! isDataView ( b ) ) return false ;
41- className = tagDataView ;
42- }
43- switch ( className ) {
44- // These types are compared by value.
23+ // Keep traversing pairs until there is nothing left to compare.
24+ while ( todo . length ) {
25+ var frame = todo . pop ( ) ;
26+ // As a special case, a single `true` on the todo means that we can
27+ // unwind the cycle detection stacks.
28+ if ( frame === true ) {
29+ // Remove the first object from the stack of traversed objects.
30+ aStack . pop ( ) ;
31+ bStack . pop ( ) ;
32+ continue ;
33+ }
34+ a = frame . a ;
35+ b = frame . b ;
36+
37+ // Identical objects are equal. `0 === -0`, but they aren't identical.
38+ // See the [Harmony `egal` proposal](https://wiki.ecmascript.org/doku.php?id=harmony:egal).
39+ if ( a === b ) {
40+ if ( a !== 0 || 1 / a === 1 / b ) continue ;
41+ return false ;
42+ }
43+ // `null` or `undefined` only equal to itself (strict comparison).
44+ if ( a == null || b == null ) return false ;
45+ // `NaN`s are equivalent, but non-reflexive.
46+ if ( a !== a ) {
47+ if ( b !== b ) continue ;
48+ return false ;
49+ }
50+ // Exhaust primitive checks
51+ var type = typeof a ;
52+ if ( type !== 'function' && type !== 'object' && typeof b != 'object' ) return false ;
53+
54+ // Unwrap any wrapped objects.
55+ if ( a instanceof _ ) a = a . _wrapped ;
56+ if ( b instanceof _ ) b = b . _wrapped ;
57+ // Compare `[[Class]]` names.
58+ var className = toString . call ( a ) ;
59+ if ( className !== toString . call ( b ) ) return false ;
60+ // Work around a bug in IE 10 - Edge 13.
61+ if ( hasDataViewBug && className == '[object Object]' && isDataView ( a ) ) {
62+ if ( ! isDataView ( b ) ) return false ;
63+ className = tagDataView ;
64+ }
65+ switch ( className ) {
66+ // These types are compared by value.
4567 case '[object RegExp]' :
4668 // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
4769 case '[object String]' :
4870 // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
4971 // equivalent to `new String("5")`.
50- return '' + a === '' + b ;
72+ if ( '' + a === '' + b ) continue ;
73+ return false ;
5174 case '[object Number]' :
52- // `NaN`s are equivalent, but non-reflexive.
53- // Object(NaN) is equivalent to NaN.
54- if ( + a !== + a ) return + b !== + b ;
55- // An `egal` comparison is performed for other numeric values.
56- return + a === 0 ? 1 / + a === 1 / b : + a === + b ;
75+ todo . push ( { a : + a , b : + b } ) ;
76+ continue ;
5777 case '[object Date]' :
5878 case '[object Boolean]' :
5979 // Coerce dates and booleans to numeric primitive values. Dates are compared by their
6080 // millisecond representations. Note that invalid dates with millisecond representations
6181 // of `NaN` are not equivalent.
62- return + a === + b ;
82+ if ( + a === + b ) continue ;
83+ return false ;
6384 case '[object Symbol]' :
64- return SymbolProto . valueOf . call ( a ) === SymbolProto . valueOf . call ( b ) ;
85+ if ( SymbolProto . valueOf . call ( a ) === SymbolProto . valueOf . call ( b ) ) continue ;
86+ return false ;
6587 case '[object ArrayBuffer]' :
6688 case tagDataView :
6789 // Coerce to typed array so we can fall through.
68- return deepEq ( toBufferView ( a ) , toBufferView ( b ) , aStack , bStack ) ;
69- }
90+ todo . push ( { a : toBufferView ( a ) , b : toBufferView ( b ) } ) ;
91+ continue ;
92+ }
7093
71- var areArrays = className === '[object Array]' ;
72- if ( ! areArrays && isTypedArray ( a ) ) {
94+ var areArrays = className === '[object Array]' ;
95+ if ( ! areArrays && isTypedArray ( a ) ) {
7396 var byteLength = getByteLength ( a ) ;
7497 if ( byteLength !== getByteLength ( b ) ) return false ;
75- if ( a . buffer === b . buffer && a . byteOffset === b . byteOffset ) return true ;
98+ if ( a . buffer === b . buffer && a . byteOffset === b . byteOffset ) continue ;
7699 areArrays = true ;
77- }
78- if ( ! areArrays ) {
79- if ( typeof a != 'object' || typeof b != 'object' ) return false ;
80-
81- // Objects with different constructors are not equivalent, but `Object`s or `Array`s
82- // from different frames are.
83- var aCtor = a . constructor , bCtor = b . constructor ;
84- if ( aCtor !== bCtor && ! ( isFunction ( aCtor ) && aCtor instanceof aCtor &&
85- isFunction ( bCtor ) && bCtor instanceof bCtor )
86- && ( 'constructor' in a && 'constructor' in b ) ) {
87- return false ;
88100 }
89- }
90- // Assume equality for cyclic structures. The algorithm for detecting cyclic
91- // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
101+ if ( ! areArrays ) {
102+ if ( typeof a != 'object' || typeof b != 'object' ) return false ;
92103
93- // Initializing stack of traversed objects.
94- // It's done here since we only need them for objects and arrays comparison.
95- aStack = aStack || [ ] ;
96- bStack = bStack || [ ] ;
97- var length = aStack . length ;
98- while ( length -- ) {
99- // Linear search. Performance is inversely proportional to the number of
100- // unique nested structures.
101- if ( aStack [ length ] === a ) return bStack [ length ] === b ;
102- }
104+ // Objects with different constructors are not equivalent, but `Object`s or `Array`s
105+ // from different frames are.
106+ var aCtor = a . constructor , bCtor = b . constructor ;
107+ if ( aCtor !== bCtor && ! ( isFunction ( aCtor ) && aCtor instanceof aCtor &&
108+ isFunction ( bCtor ) && bCtor instanceof bCtor )
109+ && ( 'constructor' in a && 'constructor' in b ) ) {
110+ return false ;
111+ }
112+ }
103113
104- // Add the first object to the stack of traversed objects.
105- aStack . push ( a ) ;
106- bStack . push ( b ) ;
114+ // Assume equality for cyclic structures. The algorithm for detecting cyclic
115+ // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
107116
108- // Recursively compare objects and arrays.
109- if ( areArrays ) {
110- // Compare array lengths to determine if a deep comparison is necessary.
111- length = a . length ;
112- if ( length !== b . length ) return false ;
113- // Deep compare the contents, ignoring non-numeric properties.
117+ var length = aStack . length ;
114118 while ( length -- ) {
115- if ( ! eq ( a [ length ] , b [ length ] , aStack , bStack ) ) return false ;
119+ // Linear search. Performance is inversely proportional to the number of
120+ // unique nested structures.
121+ if ( aStack [ length ] === a ) {
122+ // Cycle detected. Break out of the inner loop and continue the outer
123+ // loop. Step 1:
124+ if ( bStack [ length ] === b ) break ;
125+ return false ;
126+ }
116127 }
117- } else {
118- // Deep compare objects.
119- var _keys = keys ( a ) , key ;
120- length = _keys . length ;
121- // Ensure that both objects contain the same number of properties before comparing deep equality.
122- if ( keys ( b ) . length !== length ) return false ;
123- while ( length -- ) {
124- // Deep compare each member
125- key = _keys [ length ] ;
126- if ( ! ( has ( b , key ) && eq ( a [ key ] , b [ key ] , aStack , bStack ) ) ) return false ;
128+ // Step 2, use `length` to verify whether we detected a cycle:
129+ if ( length >= 0 ) continue ;
130+
131+ // Add the first object to the stack of traversed objects.
132+ aStack . push ( a ) ;
133+ bStack . push ( b ) ;
134+ // Remember to remove them again after the recursion below.
135+ todo . push ( true ) ;
136+
137+ // Recursively compare objects and arrays.
138+ if ( areArrays ) {
139+ // Compare array lengths to determine if a deep comparison is necessary.
140+ length = a . length ;
141+ if ( length !== b . length ) return false ;
142+ // Deep compare the contents, ignoring non-numeric properties.
143+ while ( length -- ) {
144+ todo . push ( { a : a [ length ] , b : b [ length ] } ) ;
145+ }
146+ } else {
147+ // Deep compare objects.
148+ var _keys = keys ( a ) , key ;
149+ length = _keys . length ;
150+ // Ensure that both objects contain the same number of properties before comparing deep equality.
151+ if ( keys ( b ) . length !== length ) return false ;
152+ while ( length -- ) {
153+ // Deep compare each member
154+ key = _keys [ length ] ;
155+ if ( ! has ( b , key ) ) return false ;
156+ todo . push ( { a : a [ key ] , b : b [ key ] } ) ;
157+ }
127158 }
128159 }
129- // Remove the first object from the stack of traversed objects.
130- aStack . pop ( ) ;
131- bStack . pop ( ) ;
160+ // We made it to the end and found no differences.
132161 return true ;
133162}
134-
135- // Perform a deep comparison to check if two objects are equal.
136- export default function isEqual ( a , b ) {
137- return eq ( a , b ) ;
138- }
0 commit comments