eql.js (4869B)
1 /*! 2 * deep-eql 3 * Copyright(c) 2013 Jake Luer <jake@alogicalparadox.com> 4 * MIT Licensed 5 */ 6 7 /*! 8 * Module dependencies 9 */ 10 11 var type = require('type-detect'); 12 13 /*! 14 * Buffer.isBuffer browser shim 15 */ 16 17 var Buffer; 18 try { Buffer = require('buffer').Buffer; } 19 catch(ex) { 20 Buffer = {}; 21 Buffer.isBuffer = function() { return false; } 22 } 23 24 /*! 25 * Primary Export 26 */ 27 28 module.exports = deepEqual; 29 30 /** 31 * Assert super-strict (egal) equality between 32 * two objects of any type. 33 * 34 * @param {Mixed} a 35 * @param {Mixed} b 36 * @param {Array} memoised (optional) 37 * @return {Boolean} equal match 38 */ 39 40 function deepEqual(a, b, m) { 41 if (sameValue(a, b)) { 42 return true; 43 } else if ('date' === type(a)) { 44 return dateEqual(a, b); 45 } else if ('regexp' === type(a)) { 46 return regexpEqual(a, b); 47 } else if (Buffer.isBuffer(a)) { 48 return bufferEqual(a, b); 49 } else if ('arguments' === type(a)) { 50 return argumentsEqual(a, b, m); 51 } else if (!typeEqual(a, b)) { 52 return false; 53 } else if (('object' !== type(a) && 'object' !== type(b)) 54 && ('array' !== type(a) && 'array' !== type(b))) { 55 return sameValue(a, b); 56 } else { 57 return objectEqual(a, b, m); 58 } 59 } 60 61 /*! 62 * Strict (egal) equality test. Ensures that NaN always 63 * equals NaN and `-0` does not equal `+0`. 64 * 65 * @param {Mixed} a 66 * @param {Mixed} b 67 * @return {Boolean} equal match 68 */ 69 70 function sameValue(a, b) { 71 if (a === b) return a !== 0 || 1 / a === 1 / b; 72 return a !== a && b !== b; 73 } 74 75 /*! 76 * Compare the types of two given objects and 77 * return if they are equal. Note that an Array 78 * has a type of `array` (not `object`) and arguments 79 * have a type of `arguments` (not `array`/`object`). 80 * 81 * @param {Mixed} a 82 * @param {Mixed} b 83 * @return {Boolean} result 84 */ 85 86 function typeEqual(a, b) { 87 return type(a) === type(b); 88 } 89 90 /*! 91 * Compare two Date objects by asserting that 92 * the time values are equal using `saveValue`. 93 * 94 * @param {Date} a 95 * @param {Date} b 96 * @return {Boolean} result 97 */ 98 99 function dateEqual(a, b) { 100 if ('date' !== type(b)) return false; 101 return sameValue(a.getTime(), b.getTime()); 102 } 103 104 /*! 105 * Compare two regular expressions by converting them 106 * to string and checking for `sameValue`. 107 * 108 * @param {RegExp} a 109 * @param {RegExp} b 110 * @return {Boolean} result 111 */ 112 113 function regexpEqual(a, b) { 114 if ('regexp' !== type(b)) return false; 115 return sameValue(a.toString(), b.toString()); 116 } 117 118 /*! 119 * Assert deep equality of two `arguments` objects. 120 * Unfortunately, these must be sliced to arrays 121 * prior to test to ensure no bad behavior. 122 * 123 * @param {Arguments} a 124 * @param {Arguments} b 125 * @param {Array} memoize (optional) 126 * @return {Boolean} result 127 */ 128 129 function argumentsEqual(a, b, m) { 130 if ('arguments' !== type(b)) return false; 131 a = [].slice.call(a); 132 b = [].slice.call(b); 133 return deepEqual(a, b, m); 134 } 135 136 /*! 137 * Get enumerable properties of a given object. 138 * 139 * @param {Object} a 140 * @return {Array} property names 141 */ 142 143 function enumerable(a) { 144 var res = []; 145 for (var key in a) res.push(key); 146 return res; 147 } 148 149 /*! 150 * Simple equality for flat iterable objects 151 * such as Arrays or Node.js buffers. 152 * 153 * @param {Iterable} a 154 * @param {Iterable} b 155 * @return {Boolean} result 156 */ 157 158 function iterableEqual(a, b) { 159 if (a.length !== b.length) return false; 160 161 var i = 0; 162 var match = true; 163 164 for (; i < a.length; i++) { 165 if (a[i] !== b[i]) { 166 match = false; 167 break; 168 } 169 } 170 171 return match; 172 } 173 174 /*! 175 * Extension to `iterableEqual` specifically 176 * for Node.js Buffers. 177 * 178 * @param {Buffer} a 179 * @param {Mixed} b 180 * @return {Boolean} result 181 */ 182 183 function bufferEqual(a, b) { 184 if (!Buffer.isBuffer(b)) return false; 185 return iterableEqual(a, b); 186 } 187 188 /*! 189 * Block for `objectEqual` ensuring non-existing 190 * values don't get in. 191 * 192 * @param {Mixed} object 193 * @return {Boolean} result 194 */ 195 196 function isValue(a) { 197 return a !== null && a !== undefined; 198 } 199 200 /*! 201 * Recursively check the equality of two objects. 202 * Once basic sameness has been established it will 203 * defer to `deepEqual` for each enumerable key 204 * in the object. 205 * 206 * @param {Mixed} a 207 * @param {Mixed} b 208 * @return {Boolean} result 209 */ 210 211 function objectEqual(a, b, m) { 212 if (!isValue(a) || !isValue(b)) { 213 return false; 214 } 215 216 if (a.prototype !== b.prototype) { 217 return false; 218 } 219 220 var i; 221 if (m) { 222 for (i = 0; i < m.length; i++) { 223 if ((m[i][0] === a && m[i][1] === b) 224 || (m[i][0] === b && m[i][1] === a)) { 225 return true; 226 } 227 } 228 } else { 229 m = []; 230 } 231 232 try { 233 var ka = enumerable(a); 234 var kb = enumerable(b); 235 } catch (ex) { 236 return false; 237 } 238 239 ka.sort(); 240 kb.sort(); 241 242 if (!iterableEqual(ka, kb)) { 243 return false; 244 } 245 246 m.push([ a, b ]); 247 248 var key; 249 for (i = ka.length - 1; i >= 0; i--) { 250 key = ka[i]; 251 if (!deepEqual(a[key], b[key], m)) { 252 return false; 253 } 254 } 255 256 return true; 257 }