mirror of
https://github.com/whoisclebs/lodash.git
synced 2026-02-12 11:57:49 +00:00
Ensure _.isEqual performs unordered comparisons of maps and sets.
This commit is contained in:
76
lodash.js
76
lodash.js
@@ -25,6 +25,10 @@
|
|||||||
ARY_FLAG = 128,
|
ARY_FLAG = 128,
|
||||||
REARG_FLAG = 256;
|
REARG_FLAG = 256;
|
||||||
|
|
||||||
|
/** Used to compose bitmasks for comparison styles. */
|
||||||
|
var UNORDERED_COMPARE_FLAG = 1,
|
||||||
|
PARTIAL_COMPARE_FLAG = 2;
|
||||||
|
|
||||||
/** Used as default options for `_.trunc`. */
|
/** Used as default options for `_.trunc`. */
|
||||||
var DEFAULT_TRUNC_LENGTH = 30,
|
var DEFAULT_TRUNC_LENGTH = 30,
|
||||||
DEFAULT_TRUNC_OMISSION = '...';
|
DEFAULT_TRUNC_OMISSION = '...';
|
||||||
@@ -2227,19 +2231,22 @@
|
|||||||
* @param {*} value The value to compare.
|
* @param {*} value The value to compare.
|
||||||
* @param {*} other The other value to compare.
|
* @param {*} other The other value to compare.
|
||||||
* @param {Function} [customizer] The function to customize comparisons.
|
* @param {Function} [customizer] The function to customize comparisons.
|
||||||
* @param {boolean} [isLoose] Specify performing partial comparisons.
|
* @param {boolean} [bitmask] The bitmask of comparison flags.
|
||||||
|
* The bitmask may be composed of the following flags:
|
||||||
|
* 1 - Unordered comparison
|
||||||
|
* 2 - Partial comparison
|
||||||
* @param {Array} [stackA] Tracks traversed `value` objects.
|
* @param {Array} [stackA] Tracks traversed `value` objects.
|
||||||
* @param {Array} [stackB] Tracks traversed `other` objects.
|
* @param {Array} [stackB] Tracks traversed `other` objects.
|
||||||
* @returns {boolean} Returns `true` if the values are equivalent, else `false`.
|
* @returns {boolean} Returns `true` if the values are equivalent, else `false`.
|
||||||
*/
|
*/
|
||||||
function baseIsEqual(value, other, customizer, isLoose, stackA, stackB) {
|
function baseIsEqual(value, other, customizer, bitmask, stackA, stackB) {
|
||||||
if (value === other) {
|
if (value === other) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (value == null || other == null || (!isObject(value) && !isObjectLike(other))) {
|
if (value == null || other == null || (!isObject(value) && !isObjectLike(other))) {
|
||||||
return value !== value && other !== other;
|
return value !== value && other !== other;
|
||||||
}
|
}
|
||||||
return baseIsEqualDeep(value, other, baseIsEqual, customizer, isLoose, stackA, stackB);
|
return baseIsEqualDeep(value, other, baseIsEqual, customizer, bitmask, stackA, stackB);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2252,12 +2259,12 @@
|
|||||||
* @param {Object} other The other object to compare.
|
* @param {Object} other The other object to compare.
|
||||||
* @param {Function} equalFunc The function to determine equivalents of values.
|
* @param {Function} equalFunc The function to determine equivalents of values.
|
||||||
* @param {Function} [customizer] The function to customize comparisons.
|
* @param {Function} [customizer] The function to customize comparisons.
|
||||||
* @param {boolean} [isLoose] Specify performing partial comparisons.
|
* @param {number} [bitmask] The bitmask of comparison flags. See `baseIsEqual` for more details.
|
||||||
* @param {Array} [stackA=[]] Tracks traversed `value` objects.
|
* @param {Array} [stackA=[]] Tracks traversed `value` objects.
|
||||||
* @param {Array} [stackB=[]] Tracks traversed `other` objects.
|
* @param {Array} [stackB=[]] Tracks traversed `other` objects.
|
||||||
* @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
|
* @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
|
||||||
*/
|
*/
|
||||||
function baseIsEqualDeep(object, other, equalFunc, customizer, isLoose, stackA, stackB) {
|
function baseIsEqualDeep(object, other, equalFunc, customizer, bitmask, stackA, stackB) {
|
||||||
var objIsArr = isArray(object),
|
var objIsArr = isArray(object),
|
||||||
othIsArr = isArray(other),
|
othIsArr = isArray(other),
|
||||||
objTag = arrayTag,
|
objTag = arrayTag,
|
||||||
@@ -2286,12 +2293,13 @@
|
|||||||
if (isSameTag && !(objIsArr || objIsObj)) {
|
if (isSameTag && !(objIsArr || objIsObj)) {
|
||||||
return equalByTag(object, other, objTag, equalFunc);
|
return equalByTag(object, other, objTag, equalFunc);
|
||||||
}
|
}
|
||||||
if (!isLoose) {
|
var isPartial = bitmask & PARTIAL_COMPARE_FLAG;
|
||||||
|
if (!isPartial) {
|
||||||
var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'),
|
var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'),
|
||||||
othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');
|
othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');
|
||||||
|
|
||||||
if (objIsWrapped || othIsWrapped) {
|
if (objIsWrapped || othIsWrapped) {
|
||||||
return equalFunc(objIsWrapped ? object.value() : object, othIsWrapped ? other.value() : other, customizer, isLoose, stackA, stackB);
|
return equalFunc(objIsWrapped ? object.value() : object, othIsWrapped ? other.value() : other, customizer, bitmask, stackA, stackB);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!isSameTag) {
|
if (!isSameTag) {
|
||||||
@@ -2312,7 +2320,7 @@
|
|||||||
stackA.push(object);
|
stackA.push(object);
|
||||||
stackB.push(other);
|
stackB.push(other);
|
||||||
|
|
||||||
var result = (objIsArr ? equalArrays : equalObjects)(object, other, equalFunc, customizer, isLoose, stackA, stackB);
|
var result = (objIsArr ? equalArrays : equalObjects)(object, other, equalFunc, customizer, bitmask, stackA, stackB);
|
||||||
|
|
||||||
stackA.pop();
|
stackA.pop();
|
||||||
stackB.pop();
|
stackB.pop();
|
||||||
@@ -2363,7 +2371,7 @@
|
|||||||
stackB = [],
|
stackB = [],
|
||||||
result = customizer ? customizer(objValue, srcValue, key, object, source, stackA, stackB) : undefined;
|
result = customizer ? customizer(objValue, srcValue, key, object, source, stackA, stackB) : undefined;
|
||||||
|
|
||||||
if (!(result === undefined ? baseIsEqual(srcValue, objValue, customizer, true, stackA, stackB) : result)) {
|
if (!(result === undefined ? baseIsEqual(srcValue, objValue, customizer, UNORDERED_COMPARE_FLAG | PARTIAL_COMPARE_FLAG, stackA, stackB) : result)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2488,7 +2496,7 @@
|
|||||||
var objValue = get(object, path);
|
var objValue = get(object, path);
|
||||||
return (objValue === undefined && objValue === srcValue)
|
return (objValue === undefined && objValue === srcValue)
|
||||||
? hasIn(object, path)
|
? hasIn(object, path)
|
||||||
: baseIsEqual(srcValue, objValue, undefined, true);
|
: baseIsEqual(srcValue, objValue, undefined, UNORDERED_COMPARE_FLAG | PARTIAL_COMPARE_FLAG);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3383,7 +3391,7 @@
|
|||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @param {Function|string} func The function or method name to reference.
|
* @param {Function|string} func The function or method name to reference.
|
||||||
* @param {number} bitmask The bitmask of flags. See `createWrapper` for more details.
|
* @param {number} bitmask The bitmask of wrapper flags. See `createWrapper` for more details.
|
||||||
* @param {*} [thisArg] The `this` binding of `func`.
|
* @param {*} [thisArg] The `this` binding of `func`.
|
||||||
* @param {Array} [partials] The arguments to prepend to those provided to the new function.
|
* @param {Array} [partials] The arguments to prepend to those provided to the new function.
|
||||||
* @param {Array} [holders] The `partials` placeholder indexes.
|
* @param {Array} [holders] The `partials` placeholder indexes.
|
||||||
@@ -3494,7 +3502,7 @@
|
|||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @param {Function} func The function to partially apply arguments to.
|
* @param {Function} func The function to partially apply arguments to.
|
||||||
* @param {number} bitmask The bitmask of flags. See `createWrapper` for more details.
|
* @param {number} bitmask The bitmask of wrapper flags. See `createWrapper` for more details.
|
||||||
* @param {*} thisArg The `this` binding of `func`.
|
* @param {*} thisArg The `this` binding of `func`.
|
||||||
* @param {Array} partials The arguments to prepend to those provided to the new function.
|
* @param {Array} partials The arguments to prepend to those provided to the new function.
|
||||||
* @returns {Function} Returns the new bound function.
|
* @returns {Function} Returns the new bound function.
|
||||||
@@ -3549,7 +3557,7 @@
|
|||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @param {Function|string} func The function or method name to reference.
|
* @param {Function|string} func The function or method name to reference.
|
||||||
* @param {number} bitmask The bitmask of flags.
|
* @param {number} bitmask The bitmask of wrapper flags.
|
||||||
* The bitmask may be composed of the following flags:
|
* The bitmask may be composed of the following flags:
|
||||||
* 1 - `_.bind`
|
* 1 - `_.bind`
|
||||||
* 2 - `_.bindKey`
|
* 2 - `_.bindKey`
|
||||||
@@ -3620,17 +3628,19 @@
|
|||||||
* @param {Array} other The other array to compare.
|
* @param {Array} other The other array to compare.
|
||||||
* @param {Function} equalFunc The function to determine equivalents of values.
|
* @param {Function} equalFunc The function to determine equivalents of values.
|
||||||
* @param {Function} [customizer] The function to customize comparisons.
|
* @param {Function} [customizer] The function to customize comparisons.
|
||||||
* @param {boolean} [isLoose] Specify performing partial comparisons.
|
* @param {number} [bitmask] The bitmask of comparison flags. See `baseIsEqual` for more details.
|
||||||
* @param {Array} [stackA] Tracks traversed `value` objects.
|
* @param {Array} [stackA] Tracks traversed `value` objects.
|
||||||
* @param {Array} [stackB] Tracks traversed `other` objects.
|
* @param {Array} [stackB] Tracks traversed `other` objects.
|
||||||
* @returns {boolean} Returns `true` if the arrays are equivalent, else `false`.
|
* @returns {boolean} Returns `true` if the arrays are equivalent, else `false`.
|
||||||
*/
|
*/
|
||||||
function equalArrays(array, other, equalFunc, customizer, isLoose, stackA, stackB) {
|
function equalArrays(array, other, equalFunc, customizer, bitmask, stackA, stackB) {
|
||||||
var index = -1,
|
var index = -1,
|
||||||
|
isPartial = bitmask & PARTIAL_COMPARE_FLAG,
|
||||||
|
isUnordered = bitmask & UNORDERED_COMPARE_FLAG,
|
||||||
arrLength = array.length,
|
arrLength = array.length,
|
||||||
othLength = other.length;
|
othLength = other.length;
|
||||||
|
|
||||||
if (arrLength != othLength && !(isLoose && othLength > arrLength)) {
|
if (arrLength != othLength && !(isPartial && othLength > arrLength)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Ignore non-index properties.
|
// Ignore non-index properties.
|
||||||
@@ -3639,7 +3649,7 @@
|
|||||||
othValue = other[index];
|
othValue = other[index];
|
||||||
|
|
||||||
if (customizer) {
|
if (customizer) {
|
||||||
var result = isLoose
|
var result = isPartial
|
||||||
? customizer(othValue, arrValue, index, other, array, stackA, stackB)
|
? customizer(othValue, arrValue, index, other, array, stackA, stackB)
|
||||||
: customizer(arrValue, othValue, index, array, other, stackA, stackB);
|
: customizer(arrValue, othValue, index, array, other, stackA, stackB);
|
||||||
}
|
}
|
||||||
@@ -3650,13 +3660,13 @@
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Recursively compare arrays (susceptible to call stack limits).
|
// Recursively compare arrays (susceptible to call stack limits).
|
||||||
if (isLoose) {
|
if (isUnordered) {
|
||||||
if (!arraySome(other, function(othValue) {
|
if (!arraySome(other, function(othValue) {
|
||||||
return arrValue === othValue || equalFunc(arrValue, othValue, customizer, isLoose, stackA, stackB);
|
return arrValue === othValue || equalFunc(arrValue, othValue, customizer, bitmask, stackA, stackB);
|
||||||
})) {
|
})) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (!(arrValue === othValue || equalFunc(arrValue, othValue, customizer, isLoose, stackA, stackB))) {
|
} else if (!(arrValue === othValue || equalFunc(arrValue, othValue, customizer, bitmask, stackA, stackB))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3701,10 +3711,11 @@
|
|||||||
return object == (other + '');
|
return object == (other + '');
|
||||||
|
|
||||||
case mapTag:
|
case mapTag:
|
||||||
return equalFunc(mapToArray(object), mapToArray(other));
|
var convert = mapToArray;
|
||||||
|
|
||||||
case setTag:
|
case setTag:
|
||||||
return equalFunc(setToArray(object), setToArray(other));
|
convert || (convert = setToArray);
|
||||||
|
return equalFunc(convert(object), convert(other), undefined, UNORDERED_COMPARE_FLAG);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -3718,40 +3729,44 @@
|
|||||||
* @param {Object} other The other object to compare.
|
* @param {Object} other The other object to compare.
|
||||||
* @param {Function} equalFunc The function to determine equivalents of values.
|
* @param {Function} equalFunc The function to determine equivalents of values.
|
||||||
* @param {Function} [customizer] The function to customize comparisons.
|
* @param {Function} [customizer] The function to customize comparisons.
|
||||||
* @param {boolean} [isLoose] Specify performing partial comparisons.
|
* @param {number} [bitmask] The bitmask of comparison flags. See `baseIsEqual` for more details.
|
||||||
|
* @param {boolean} [bitmask] Specify performing partial comparisons.
|
||||||
* @param {Array} [stackA] Tracks traversed `value` objects.
|
* @param {Array} [stackA] Tracks traversed `value` objects.
|
||||||
* @param {Array} [stackB] Tracks traversed `other` objects.
|
* @param {Array} [stackB] Tracks traversed `other` objects.
|
||||||
* @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
|
* @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
|
||||||
*/
|
*/
|
||||||
function equalObjects(object, other, equalFunc, customizer, isLoose, stackA, stackB) {
|
function equalObjects(object, other, equalFunc, customizer, bitmask, stackA, stackB) {
|
||||||
var objProps = keys(object),
|
var isPartial = bitmask & PARTIAL_COMPARE_FLAG,
|
||||||
|
isUnordered = bitmask & UNORDERED_COMPARE_FLAG,
|
||||||
|
objProps = keys(object),
|
||||||
objLength = objProps.length,
|
objLength = objProps.length,
|
||||||
othProps = keys(other),
|
othProps = keys(other),
|
||||||
othLength = othProps.length;
|
othLength = othProps.length;
|
||||||
|
|
||||||
if (objLength != othLength && !isLoose) {
|
if (objLength != othLength && !isPartial) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var index = objLength;
|
var index = objLength;
|
||||||
while (index--) {
|
while (index--) {
|
||||||
var key = objProps[index];
|
var key = objProps[index];
|
||||||
if (!(isLoose ? key in other : hasOwnProperty.call(other, key))) {
|
if (!(isPartial ? key in other : hasOwnProperty.call(other, key)) ||
|
||||||
|
!(isUnordered || key == othProps[index])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var skipCtor = isLoose;
|
var skipCtor = isPartial;
|
||||||
while (++index < objLength) {
|
while (++index < objLength) {
|
||||||
key = objProps[index];
|
key = objProps[index];
|
||||||
var objValue = object[key],
|
var objValue = object[key],
|
||||||
othValue = other[key];
|
othValue = other[key];
|
||||||
|
|
||||||
if (customizer) {
|
if (customizer) {
|
||||||
var result = isLoose
|
var result = isPartial
|
||||||
? customizer(othValue, objValue, key, other, object, stackA, stackB)
|
? customizer(othValue, objValue, key, other, object, stackA, stackB)
|
||||||
: customizer(objValue, othValue, key, object, other, stackA, stackB);
|
: customizer(objValue, othValue, key, object, other, stackA, stackB);
|
||||||
}
|
}
|
||||||
// Recursively compare objects (susceptible to call stack limits).
|
// Recursively compare objects (susceptible to call stack limits).
|
||||||
if (!(result === undefined ? equalFunc(objValue, othValue, customizer, isLoose, stackA, stackB) : result)) {
|
if (!(result === undefined ? equalFunc(objValue, othValue, customizer, bitmask, stackA, stackB) : result)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
skipCtor || (skipCtor = key == 'constructor');
|
skipCtor || (skipCtor = key == 'constructor');
|
||||||
@@ -3999,7 +4014,6 @@
|
|||||||
return new Ctor(object);
|
return new Ctor(object);
|
||||||
|
|
||||||
case setTag:
|
case setTag:
|
||||||
console.log(object)
|
|
||||||
return createSet(setToArray(object));
|
return createSet(setToArray(object));
|
||||||
|
|
||||||
case regexpTag:
|
case regexpTag:
|
||||||
|
|||||||
24
test/test.js
24
test/test.js
@@ -6788,28 +6788,28 @@
|
|||||||
strictEqual(_.isEqual(a, b), false);
|
strictEqual(_.isEqual(a, b), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should compare maps', 3, function() {
|
test('should compare maps', 4, function() {
|
||||||
if (Map) {
|
if (Map) {
|
||||||
var map1 = new Map,
|
var map1 = new Map,
|
||||||
map2 = new Map;
|
map2 = new Map;
|
||||||
|
|
||||||
map1.set('a', 1);
|
map1.set('a', 1);
|
||||||
map2.set('b', 2);
|
map2.set('b', 2);
|
||||||
|
|
||||||
strictEqual(_.isEqual(map1, map2), false);
|
strictEqual(_.isEqual(map1, map2), false);
|
||||||
|
|
||||||
map1.set('b', 2);
|
map1.set('b', 2);
|
||||||
map2.set('a', 1);
|
map2.set('a', 1);
|
||||||
|
strictEqual(_.isEqual(map1, map2), true);
|
||||||
strictEqual(_.isEqual(map1, map2), false);
|
|
||||||
|
|
||||||
map1['delete']('a');
|
map1['delete']('a');
|
||||||
map1.set('a', 1);
|
map1.set('a', 1);
|
||||||
|
|
||||||
strictEqual(_.isEqual(map1, map2), true);
|
strictEqual(_.isEqual(map1, map2), true);
|
||||||
|
|
||||||
|
map2['delete']('a');
|
||||||
|
strictEqual(_.isEqual(map1, map2), false);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
skipTest(3);
|
skipTest(4);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -6821,28 +6821,28 @@
|
|||||||
strictEqual(_.isEqual(/x/g, { 'global': true, 'ignoreCase': false, 'multiline': false, 'source': 'x' }), false);
|
strictEqual(_.isEqual(/x/g, { 'global': true, 'ignoreCase': false, 'multiline': false, 'source': 'x' }), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should compare sets', 3, function() {
|
test('should compare sets', 4, function() {
|
||||||
if (Set) {
|
if (Set) {
|
||||||
var set1 = new Set,
|
var set1 = new Set,
|
||||||
set2 = new Set;
|
set2 = new Set;
|
||||||
|
|
||||||
set1.add(1);
|
set1.add(1);
|
||||||
set2.add(2);
|
set2.add(2);
|
||||||
|
|
||||||
strictEqual(_.isEqual(set1, set2), false);
|
strictEqual(_.isEqual(set1, set2), false);
|
||||||
|
|
||||||
set1.add(2);
|
set1.add(2);
|
||||||
set2.add(1);
|
set2.add(1);
|
||||||
|
strictEqual(_.isEqual(set1, set2), true);
|
||||||
strictEqual(_.isEqual(set1, set2), false);
|
|
||||||
|
|
||||||
set1['delete'](1);
|
set1['delete'](1);
|
||||||
set1.add(1);
|
set1.add(1);
|
||||||
|
|
||||||
strictEqual(_.isEqual(set1, set2), true);
|
strictEqual(_.isEqual(set1, set2), true);
|
||||||
|
|
||||||
|
set2['delete'](1);
|
||||||
|
strictEqual(_.isEqual(set1, set2), false);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
skipTest(3);
|
skipTest(4);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user