deepy
Version:
A fast algorithm for comparing equality of values with strict equality for value types.
191 lines (182 loc) • 6.13 kB
JavaScript
'use strict';
var util = require('util');
function isBuffer (value) {
if (global.Buffer && typeof global.Buffer.isBuffer === 'function') {
return global.Buffer.isBuffer(value);
}
return !!(value != null && value._isBuffer);
};
function compare (a, b) {
if (a === b) {
return 0;
}
var x = a.length;
var y = b.length;
for (var i = 0, len = Math.min(x, y); i < len; ++i) {
if (a[i] !== b[i]) {
x = a[i];
y = b[i];
break;
}
}
if (x < y) {
return -1;
}
if (y < x) {
return 1;
}
return 0;
};
function isView (arrbuf) {
if (isBuffer(arrbuf)) {
return false;
}
if (typeof global.ArrayBuffer !== 'function') {
return false;
}
if (typeof ArrayBuffer.isView === 'function') {
return ArrayBuffer.isView(arrbuf);
}
if (!arrbuf) {
return false;
}
if (arrbuf instanceof DataView) {
return true;
}
if (arrbuf.buffer && arrbuf.buffer instanceof ArrayBuffer) {
return true;
}
return false;
};
function callToString (obj) { return Object.prototype.toString.call(obj); };
function isObject (value) {
var type = typeof value;
/* tslint:disable */
return !!value && (type == 'object' || type == 'function');
/* tslint:enable */
};
/* tslint:disable */
function isArguments (object) { return Object.prototype.toString.call(object) == '[object Arguments]'; };
/* tslint:enable */
function isObjectLike(value) {
/* tslint:disable */
return !!value && typeof value == 'object';
/* tslint:enable */
}
function deepy(actual, expected, strict, memos) {
if (actual === expected) {
return true;
}
// Identical objects are equal. `0 === -0`, but they aren't identical.
// See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
if (actual === expected) {
/* tslint:disable */
return actual !== 0 || 1 / actual == 1 / expected;
}
if (actual == null || expected == null || (!isObject(actual) && !isObjectLike(expected))) {
return actual !== actual && expected !== expected;
}
if (actual instanceof Error && expected instanceof Error) {
/* tslint:disable */
return actual.message == expected.message;
}
if (isBuffer(actual) && isBuffer(expected)) {
return compare(actual, expected) === 0;
}
if (util.isDate(actual) && util.isDate(expected)) {
return actual.getTime() === expected.getTime();
}
if (util.isRegExp(actual) && util.isRegExp(expected)) {
return actual.source === expected.source &&
actual.global === expected.global &&
actual.multiline === expected.multiline &&
actual.lastIndex === expected.lastIndex &&
actual.ignoreCase === expected.ignoreCase;
}
if ((actual === null || typeof actual !== 'object') &&
(expected === null || typeof expected !== 'object')) {
/* tslint:disable */
return strict ? actual === expected : actual == expected;
}
var className = toString.call(actual);
if (className !== toString.call(expected)) {
return false;
}
if (className === '[object RegExp]' || className === '[object String]') {
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
// equivalent to `new String("5")`.
return '' + actual === '' + expected;
}
if (className === '[object Number]') {
// `NaN`s are equivalent, but non-reflexive.
// Object(NaN) is equivalent to NaN
if (+actual !== +actual) {
return +expected !== +expected;
}
// An `egal` comparison is performed for other numeric values.
return +actual === 0 ? 1 / +actual === 1 / expected : +actual === +expected;
}
if (className === '[object Boolean]') {
// Coerce dates and booleans to numeric primitive values. Dates are compared by their
// millisecond representations. Note that invalid dates with millisecond representations
// of `NaN` are not equivalent.
return +actual === +expected;
}
if (isView(actual) && isView(expected) &&
callToString(actual) === callToString(expected) &&
!(actual instanceof Float32Array ||
actual instanceof Float64Array)) {
return compare(new Uint8Array(actual.buffer), new Uint8Array(expected.buffer)) === 0;
}
if (isBuffer(actual) !== isBuffer(expected)) {
return false;
}
memos = memos || { actual: [], expected: [] };
var actualIndex = memos.actual.indexOf(actual);
if (actualIndex !== -1) {
if (actualIndex === memos.expected.indexOf(expected)) {
return true;
}
}
memos.actual.push(actual);
memos.expected.push(expected);
if (actual === null || actual === undefined || expected === null || expected === undefined) {
return false;
}
if (strict && Object.getPrototypeOf(actual) !== Object.getPrototypeOf(expected)) {
return false;
}
var aIsArgs = isArguments(actual);
var bIsArgs = isArguments(expected);
if ((aIsArgs && !bIsArgs) || (!aIsArgs && bIsArgs)) {
return false;
}
var ka = Object.keys(actual);
var kb = Object.keys(expected);
var key;
var i;
// having the same number of owned properties (keys incorporates
// hasOwnProperty)
if (ka.length !== kb.length) {
return false;
}
// the same set of keys (although not necessarily the same order),
ka.sort();
kb.sort();
// ~~~cheap key test
for (i = ka.length - 1; i >= 0; i--) {
if (ka[i] !== kb[i]) {
return false;
}
}
// equivalent values for every corresponding key, and
// ~~~possibly expensive deep test
for (i = ka.length - 1; i >= 0; i--) {
key = ka[i];
if (!deepy(actual[key], expected[key], strict, memos)) {
return false;
}
}
return true;
}
module.exports = deepy;