yoqw/web/cms/app/utils/equals.js
2020-07-01 15:22:07 +08:00

179 lines
5.8 KiB
JavaScript

/* -----------------------------------------------------------------------------------------
equals( a, b [, enforce_properties_order, cyclic] )
Returns true if a and b are deeply equal, false otherwise.
Parameters:
- a (Any type): value to compare to b
- b (Any type): value compared to a
Optional Parameters:
- enforce_properties_order (Boolean): true to check if Object properties are provided
in the same order between a and b
- cyclic (Boolean): true to check for cycles in cyclic objects
Implementation:
'a' is considered equal to 'b' if all scalar values in a and b are strictly equal as
compared with operator '===' except for these two special cases:
- 0 === -0 but are not equal.
- NaN is not === to itself but is equal.
RegExp objects are considered equal if they have the same lastIndex, i.e. both regular
expressions have matched the same number of times.
Functions must be identical, so that they have the same closure context.
"undefined" is a valid value, including in Objects
106 automated tests.
Provide options for slower, less-common use cases:
- Unless enforce_properties_order is true, if 'a' and 'b' are non-Array Objects, the
order of occurence of their attributes is considered irrelevant:
{ a: 1, b: 2 } is considered equal to { b: 2, a: 1 }
- Unless cyclic is true, Cyclic objects will throw:
RangeError: Maximum call stack size exceeded
*/
export default function equals( a, b, enforce_properties_order, cyclic ) {
return a === b // strick equality should be enough unless zero
&& a !== 0 // because 0 === -0, requires test by _equals()
|| _equals( a, b ) // handles not strictly equal or zero values
;
function _equals( a, b ) {
// a and b have already failed test for strict equality or are zero
var s, l, p, x, y;
// They should have the same toString() signature
if ( ( s = toString.call( a ) ) !== toString.call( b ) ) return false;
switch( s ) {
default: // Boolean, Date, String
return a.valueOf() === b.valueOf();
case '[object Number]':
// Converts Number instances into primitive values
// This is required also for NaN test bellow
a = +a;
b = +b;
return a ? // a is Non-zero and Non-NaN
a === b
: // a is 0, -0 or NaN
a === a ? // a is 0 or -O
1/a === 1/b // 1/0 !== 1/-0 because Infinity !== -Infinity
: b !== b // NaN, the only Number not equal to itself!
;
// [object Number]
case '[object RegExp]':
return a.source == b.source
&& a.global == b.global
&& a.ignoreCase == b.ignoreCase
&& a.multiline == b.multiline
&& a.lastIndex == b.lastIndex
;
// [object RegExp]
case '[object Function]':
return false; // functions should be strictly equal because of closure context
// [object Function]
case '[object Array]':
if ( cyclic && ( x = reference_equals( a, b ) ) !== null ) return x; // intentionally duplicated bellow for [object Object]
if ( ( l = a.length ) != b.length ) return false;
// Both have as many elements
while ( l-- ) {
if ( ( x = a[ l ] ) === ( y = b[ l ] ) && x !== 0 || _equals( x, y ) ) continue;
return false;
}
return true;
// [object Array]
case '[object Object]':
if ( cyclic && ( x = reference_equals( a, b ) ) !== null ) return x; // intentionally duplicated from above for [object Array]
l = 0; // counter of own properties
if ( enforce_properties_order ) {
var properties = [];
for ( p in a ) {
if ( a.hasOwnProperty( p ) ) {
properties.push( p );
if ( ( x = a[ p ] ) === ( y = b[ p ] ) && x !== 0 || _equals( x, y ) ) continue;
return false;
}
}
// Check if 'b' has as the same properties as 'a' in the same order
for ( p in b )
if ( b.hasOwnProperty( p ) && properties[ l++ ] != p )
return false;
} else {
for ( p in a ) {
if ( a.hasOwnProperty( p ) ) {
++l;
if ( ( x = a[ p ] ) === ( y = b[ p ] ) && x !== 0 || _equals( x, y ) ) continue;
return false;
}
}
// Check if 'b' has as not more own properties than 'a'
for ( p in b )
if ( b.hasOwnProperty( p ) && --l < 0 )
return false;
}
return true;
// [object Object]
} // switch toString.call( a )
} // _equals()
/* -----------------------------------------------------------------------------------------
reference_equals( a, b )
Helper function to compare object references on cyclic objects or arrays.
Returns:
- null if a or b is not part of a cycle, adding them to object_references array
- true: same cycle found for a and b
- false: different cycle found for a and b
On the first call of a specific invocation of equal(), replaces self with inner function
holding object_references array object in closure context.
This allows to create a context only if and when an invocation of equal() compares
objects or arrays.
*/
function reference_equals( a, b ) {
var object_references = [];
return ( reference_equals = _reference_equals )( a, b );
function _reference_equals( a, b ) {
var l = object_references.length;
while ( l-- )
if ( object_references[ l-- ] === b )
return object_references[ l ] === a;
object_references.push( a, b );
return null;
} // _reference_equals()
} // reference_equals()
} // equals()