|
|
'use strict'
|
|
|
|
|
|
var bufferEquals = require('buffer-equals')
|
|
|
|
|
|
function isArguments (object) {
|
|
|
return Object.prototype.toString.call(object) === '[object Arguments]'
|
|
|
}
|
|
|
|
|
|
module.exports = shallower
|
|
|
|
|
|
function shallower (a, b) {
|
|
|
return shallower_(a, b, [], [])
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Based on `only-shallow` by @othiym23. The comments are mostly his, edited
|
|
|
* to reflect the usage of strict equality.
|
|
|
*
|
|
|
* This is a structural equality test, modeled on bits and pieces of loads of
|
|
|
* other implementations of this algorithm, most notably the much stricter
|
|
|
* `deeper`, from which this comment was copied.
|
|
|
*
|
|
|
* Everybody who writes one of these functions puts the documentation
|
|
|
* inline, which makes it incredibly hard to follow. Here's what this version
|
|
|
* of the algorithm does, in order:
|
|
|
*
|
|
|
* 1. Use strict equality (`===`).
|
|
|
* 2. `null` *is* an object – a singleton value object, in fact – so if
|
|
|
* either is `null`, return a === b.
|
|
|
* 3. Since the only way to make it this far is for `a` or `b` to be an object, if
|
|
|
* `a` or `b` is *not* an object, they're clearly not the same.
|
|
|
* 4. It's much faster to compare dates by numeric value than by lexical value.
|
|
|
* 5. Same goes for Regexps.
|
|
|
* 6. The parts of an arguments list most people care about are the arguments
|
|
|
* themselves, not the callee, which you shouldn't be looking at anyway.
|
|
|
* 7. Objects are more complex:
|
|
|
* a. Return `true` if `a` and `b` both have no properties.
|
|
|
* b. Ensure that `a` and `b` have the same number of own properties with the
|
|
|
* same names (which is what `Object.keys()` returns).
|
|
|
* c. Ensure that cyclical references don't blow up the stack.
|
|
|
* d. Ensure that all the key names match (faster).
|
|
|
* e. Ensure that all of the associated values match, recursively (slower).
|
|
|
*/
|
|
|
function shallower_ (a, b, ca, cb) {
|
|
|
if (typeof a !== 'object' && typeof b !== 'object' && a === b) {
|
|
|
return true
|
|
|
} else if (a === null || b === null) {
|
|
|
return a === b
|
|
|
} else if (typeof a !== 'object' || typeof b !== 'object') {
|
|
|
return false
|
|
|
} else if (Buffer.isBuffer(a) && Buffer.isBuffer(b)) {
|
|
|
return bufferEquals(a, b)
|
|
|
} else if (a instanceof Date && b instanceof Date) {
|
|
|
return a.getTime() === b.getTime()
|
|
|
} else if (a instanceof RegExp && b instanceof RegExp) {
|
|
|
return a.source === b.source &&
|
|
|
a.global === b.global &&
|
|
|
a.multiline === b.multiline &&
|
|
|
a.lastIndex === b.lastIndex &&
|
|
|
a.ignoreCase === b.ignoreCase
|
|
|
} else if (isArguments(a) || isArguments(b)) {
|
|
|
var slice = Array.prototype.slice
|
|
|
return shallower_(slice.call(a), slice.call(b), ca, cb)
|
|
|
} else {
|
|
|
if (Array.isArray(a) !== Array.isArray(b)) return false
|
|
|
|
|
|
var ka = Object.keys(a)
|
|
|
var kb = Object.keys(b)
|
|
|
// don't bother with stack acrobatics if there's nothing there
|
|
|
if (ka.length === 0 && kb.length === 0) return true
|
|
|
if (ka.length !== kb.length) return false
|
|
|
|
|
|
var cal = ca.length
|
|
|
while (cal--) if (ca[cal] === a) return cb[cal] === b
|
|
|
ca.push(a); cb.push(b)
|
|
|
|
|
|
ka.sort(); kb.sort()
|
|
|
for (var k = ka.length - 1; k >= 0; k--) if (ka[k] !== kb[k]) return false
|
|
|
|
|
|
var key
|
|
|
for (var l = ka.length - 1; l >= 0; l--) {
|
|
|
key = ka[l]
|
|
|
if (!shallower_(a[key], b[key], ca, cb)) return false
|
|
|
}
|
|
|
|
|
|
ca.pop(); cb.pop()
|
|
|
|
|
|
return true
|
|
|
}
|
|
|
}
|