1+
2+ // Tests for equality any JavaScript type and structure without unexpected results.
3+ // Discussions and reference: http://philrathe.com/articles/equiv
4+ // Test suites: http://philrathe.com/tests/equiv
5+ // Author: Philippe RathŽ <prathe@gmail.com>
6+ window . equiv = function ( ) {
7+
8+ var innerEquiv ; // the real equiv function
9+ var callers = [ ] ; // stack to decide between skip/abort functions
10+
11+ // Determine what is o.
12+ function hoozit ( o ) {
13+ if ( typeof o === "string" ) {
14+ return "string" ;
15+
16+ } else if ( typeof o === "boolean" ) {
17+ return "boolean" ;
18+
19+ } else if ( typeof o === "number" ) {
20+
21+ if ( isNaN ( o ) ) {
22+ return "nan" ;
23+ } else {
24+ return "number" ;
25+ }
26+
27+ } else if ( typeof o === "undefined" ) {
28+ return "undefined" ;
29+
30+ // consider: typeof null === object
31+ } else if ( o === null ) {
32+ return "null" ;
33+
34+ // consider: typeof [] === object
35+ } else if ( o instanceof Array ) {
36+ return "array" ;
37+
38+ // consider: typeof new Date() === object
39+ } else if ( o instanceof Date ) {
40+ return "date" ;
41+
42+ // consider: /./ instanceof Object;
43+ // /./ instanceof RegExp;
44+ // typeof /./ === "function"; // => false in IE and Opera,
45+ // true in FF and Safari
46+ } else if ( o instanceof RegExp ) {
47+ return "regexp" ;
48+
49+ } else if ( typeof o === "object" ) {
50+ return "object" ;
51+
52+ } else if ( o instanceof Function ) {
53+ return "function" ;
54+ }
55+ }
56+
57+ // Call the o related callback with the given arguments.
58+ function bindCallbacks ( o , callbacks , args ) {
59+ var prop = hoozit ( o ) ;
60+ if ( prop ) {
61+ if ( hoozit ( callbacks [ prop ] ) === "function" ) {
62+ return callbacks [ prop ] . apply ( callbacks , args ) ;
63+ } else {
64+ return callbacks [ prop ] ; // or undefined
65+ }
66+ }
67+ }
68+
69+ var callbacks = function ( ) {
70+
71+ // for string, boolean, number and null
72+ function useStrictEquality ( b , a ) {
73+ return a === b ;
74+ }
75+
76+ return {
77+ "string" : useStrictEquality ,
78+ "boolean" : useStrictEquality ,
79+ "number" : useStrictEquality ,
80+ "null" : useStrictEquality ,
81+ "undefined" : useStrictEquality ,
82+
83+ "nan" : function ( b ) {
84+ return isNaN ( b ) ;
85+ } ,
86+
87+ "date" : function ( b , a ) {
88+ return hoozit ( b ) === "date" && a . valueOf ( ) === b . valueOf ( ) ;
89+ } ,
90+
91+ "regexp" : function ( b , a ) {
92+ return hoozit ( b ) === "regexp" &&
93+ a . source === b . source && // the regex itself
94+ a . global === b . global && // and its modifers (gmi) ...
95+ a . ignoreCase === b . ignoreCase &&
96+ a . multiline === b . multiline ;
97+ } ,
98+
99+ // - skip when the property is a method of an instance (OOP)
100+ // - abort otherwise,
101+ // initial === would have catch identical references anyway
102+ "function" : function ( ) {
103+ var caller = callers [ callers . length - 1 ] ;
104+ return caller !== Object &&
105+ typeof caller !== "undefined" ;
106+ } ,
107+
108+ "array" : function ( b , a ) {
109+ var i ;
110+ var len ;
111+
112+ // b could be an object literal here
113+ if ( ! ( hoozit ( b ) === "array" ) ) {
114+ return false ;
115+ }
116+
117+ len = a . length ;
118+ if ( len !== b . length ) { // safe and faster
119+ return false ;
120+ }
121+ for ( i = 0 ; i < len ; i ++ ) {
122+ if ( ! innerEquiv ( a [ i ] , b [ i ] ) ) {
123+ return false ;
124+ }
125+ }
126+ return true ;
127+ } ,
128+
129+ "object" : function ( b , a ) {
130+ var i ;
131+ var eq = true ; // unless we can proove it
132+ var aProperties = [ ] , bProperties = [ ] ; // collection of strings
133+
134+ // comparing constructors is more strict than using instanceof
135+ if ( a . constructor !== b . constructor ) {
136+ return false ;
137+ }
138+
139+ // stack constructor before traversing properties
140+ callers . push ( a . constructor ) ;
141+
142+ for ( i in a ) { // be strict: don't ensures hasOwnProperty and go deep
143+
144+ aProperties . push ( i ) ; // collect a's properties
145+
146+ if ( ! innerEquiv ( a [ i ] , b [ i ] ) ) {
147+ eq = false ;
148+ }
149+ }
150+
151+ callers . pop ( ) ; // unstack, we are done
152+
153+ for ( i in b ) {
154+ bProperties . push ( i ) ; // collect b's properties
155+ }
156+
157+ // Ensures identical properties name
158+ return eq && innerEquiv ( aProperties . sort ( ) , bProperties . sort ( ) ) ;
159+ }
160+ } ;
161+ } ( ) ;
162+
163+ innerEquiv = function ( ) { // can take multiple arguments
164+ var args = Array . prototype . slice . apply ( arguments ) ;
165+ if ( args . length < 2 ) {
166+ return true ; // end transition
167+ }
168+
169+ return ( function ( a , b ) {
170+ if ( a === b ) {
171+ return true ; // catch the most you can
172+
173+ } else if ( typeof a !== typeof b || a === null || b === null || typeof a === "undefined" || typeof b === "undefined" ) {
174+ return false ; // don't lose time with error prone cases
175+
176+ } else {
177+ return bindCallbacks ( a , callbacks , [ b , a ] ) ;
178+ }
179+
180+ // apply transition with (1..n) arguments
181+ } ) ( args [ 0 ] , args [ 1 ] ) && arguments . callee . apply ( this , args . splice ( 1 , args . length - 1 ) ) ;
182+ } ;
183+
184+ return innerEquiv ;
185+ } ( ) ; // equiv
0 commit comments