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