258 lines
4.8 KiB
JavaScript
258 lines
4.8 KiB
JavaScript
/*!
|
|
* deep-eql
|
|
* Copyright(c) 2013 Jake Luer <jake@alogicalparadox.com>
|
|
* 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;
|
|
}
|