/*! * deep-eql * Copyright(c) 2013 Jake Luer * MIT Licensed */ /*! * Module dependencies */ var type = require('type-detect'); /*! * Buffer.isBuffer browser shim */ var Buffer; try { Buffer = require('buffer').Buffer; } catch(ex) { Buffer = {}; Buffer.isBuffer = function() { return false; } } /*! * Primary Export */ module.exports = deepEqual; /** * Assert super-strict (egal) equality between * two objects of any type. * * @param {Mixed} a * @param {Mixed} b * @param {Array} memoised (optional) * @return {Boolean} equal match */ function deepEqual(a, b, m) { if (sameValue(a, b)) { return true; } else if ('date' === type(a)) { return dateEqual(a, b); } else if ('regexp' === type(a)) { return regexpEqual(a, b); } else if (Buffer.isBuffer(a)) { return bufferEqual(a, b); } else if ('arguments' === type(a)) { return argumentsEqual(a, b, m); } else if (!typeEqual(a, b)) { return false; } else if (('object' !== type(a) && 'object' !== type(b)) && ('array' !== type(a) && 'array' !== type(b))) { return sameValue(a, b); } else { return objectEqual(a, b, m); } } /*! * Strict (egal) equality test. Ensures that NaN always * equals NaN and `-0` does not equal `+0`. * * @param {Mixed} a * @param {Mixed} b * @return {Boolean} equal match */ function sameValue(a, b) { if (a === b) return a !== 0 || 1 / a === 1 / b; return a !== a && b !== b; } /*! * Compare the types of two given objects and * return if they are equal. Note that an Array * has a type of `array` (not `object`) and arguments * have a type of `arguments` (not `array`/`object`). * * @param {Mixed} a * @param {Mixed} b * @return {Boolean} result */ function typeEqual(a, b) { return type(a) === type(b); } /*! * Compare two Date objects by asserting that * the time values are equal using `saveValue`. * * @param {Date} a * @param {Date} b * @return {Boolean} result */ function dateEqual(a, b) { if ('date' !== type(b)) return false; return sameValue(a.getTime(), b.getTime()); } /*! * Compare two regular expressions by converting them * to string and checking for `sameValue`. * * @param {RegExp} a * @param {RegExp} b * @return {Boolean} result */ function regexpEqual(a, b) { if ('regexp' !== type(b)) return false; return sameValue(a.toString(), b.toString()); } /*! * Assert deep equality of two `arguments` objects. * Unfortunately, these must be sliced to arrays * prior to test to ensure no bad behavior. * * @param {Arguments} a * @param {Arguments} b * @param {Array} memoize (optional) * @return {Boolean} result */ function argumentsEqual(a, b, m) { if ('arguments' !== type(b)) return false; a = [].slice.call(a); b = [].slice.call(b); return deepEqual(a, b, m); } /*! * Get enumerable properties of a given object. * * @param {Object} a * @return {Array} property names */ function enumerable(a) { var res = []; for (var key in a) res.push(key); return res; } /*! * Simple equality for flat iterable objects * such as Arrays or Node.js buffers. * * @param {Iterable} a * @param {Iterable} b * @return {Boolean} result */ function iterableEqual(a, b) { if (a.length !== b.length) return false; var i = 0; var match = true; for (; i < a.length; i++) { if (a[i] !== b[i]) { match = false; break; } } return match; } /*! * Extension to `iterableEqual` specifically * for Node.js Buffers. * * @param {Buffer} a * @param {Mixed} b * @return {Boolean} result */ function bufferEqual(a, b) { if (!Buffer.isBuffer(b)) return false; return iterableEqual(a, b); } /*! * Block for `objectEqual` ensuring non-existing * values don't get in. * * @param {Mixed} object * @return {Boolean} result */ function isValue(a) { return a !== null && a !== undefined; } /*! * Recursively check the equality of two objects. * Once basic sameness has been established it will * defer to `deepEqual` for each enumerable key * in the object. * * @param {Mixed} a * @param {Mixed} b * @return {Boolean} result */ function objectEqual(a, b, m) { if (!isValue(a) || !isValue(b)) { return false; } if (a.prototype !== b.prototype) { return false; } var i; if (m) { for (i = 0; i < m.length; i++) { if ((m[i][0] === a && m[i][1] === b) || (m[i][0] === b && m[i][1] === a)) { return true; } } } else { m = []; } try { var ka = enumerable(a); var kb = enumerable(b); } catch (ex) { return false; } ka.sort(); kb.sort(); if (!iterableEqual(ka, kb)) { return false; } m.push([ a, b ]); var key; for (i = ka.length - 1; i >= 0; i--) { key = ka[i]; if (!deepEqual(a[key], b[key], m)) { return false; } } return true; }