Loosen _.matches to match objects with inherited properties. [closes #1067]

This commit is contained in:
jdalton
2015-03-21 23:33:19 -07:00
parent 8930e6b393
commit f20d8f5cc0
2 changed files with 40 additions and 34 deletions

View File

@@ -2265,12 +2265,12 @@
* @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 comparing values. * @param {Function} [customizer] The function to customize comparing values.
* @param {boolean} [isWhere] Specify performing partial comparisons. * @param {boolean} [isLoose] 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 values are equivalent, else `false`. * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
*/ */
function baseIsEqual(value, other, customizer, isWhere, stackA, stackB) { function baseIsEqual(value, other, customizer, isLoose, stackA, stackB) {
// Exit early for identical values. // Exit early for identical values.
if (value === other) { if (value === other) {
// Treat `+0` vs. `-0` as not equal. // Treat `+0` vs. `-0` as not equal.
@@ -2285,7 +2285,7 @@
// Return `false` unless both values are `NaN`. // Return `false` unless both values are `NaN`.
return value !== value && other !== other; return value !== value && other !== other;
} }
return baseIsEqualDeep(value, other, baseIsEqual, customizer, isWhere, stackA, stackB); return baseIsEqualDeep(value, other, baseIsEqual, customizer, isLoose, stackA, stackB);
} }
/** /**
@@ -2298,12 +2298,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 comparing objects. * @param {Function} [customizer] The function to customize comparing objects.
* @param {boolean} [isWhere] Specify performing partial comparisons. * @param {boolean} [isLoose] 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 baseIsEqualDeep(object, other, equalFunc, customizer, isWhere, stackA, stackB) { function baseIsEqualDeep(object, other, equalFunc, customizer, isLoose, stackA, stackB) {
var objIsArr = isArray(object), var objIsArr = isArray(object),
othIsArr = isArray(other), othIsArr = isArray(other),
objTag = arrayTag, objTag = arrayTag,
@@ -2325,21 +2325,27 @@
othIsArr = isTypedArray(other); othIsArr = isTypedArray(other);
} }
} }
var objIsObj = objTag == objectTag && !isHostObject(object), var objIsObj = (objTag == objectTag || (isLoose && objTag == funcTag)) && !isHostObject(object),
othIsObj = othTag == objectTag && !isHostObject(other), othIsObj = othTag == objectTag && !isHostObject(other),
isSameTag = objTag == othTag; isSameTag = objTag == othTag;
if (isSameTag && !(objIsArr || objIsObj)) { if (isSameTag && !(objIsArr || objIsObj)) {
return equalByTag(object, other, objTag); return equalByTag(object, other, objTag);
} }
var valWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'), if (isLoose) {
othWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__'); if (!isSameTag && !(objIsObj && othIsObj)) {
return false;
}
} else {
var valWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'),
othWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');
if (valWrapped || othWrapped) { if (valWrapped || othWrapped) {
return equalFunc(valWrapped ? object.value() : object, othWrapped ? other.value() : other, customizer, isWhere, stackA, stackB); return equalFunc(valWrapped ? object.value() : object, othWrapped ? other.value() : other, customizer, isLoose, stackA, stackB);
} }
if (!isSameTag) { if (!isSameTag) {
return false; return false;
}
} }
// Assume cyclic values are equal. // Assume cyclic values are equal.
// For more information on detecting circular references see https://es5.github.io/#JO. // For more information on detecting circular references see https://es5.github.io/#JO.
@@ -2356,7 +2362,7 @@
stackA.push(object); stackA.push(object);
stackB.push(other); stackB.push(other);
var result = (objIsArr ? equalArrays : equalObjects)(object, other, equalFunc, customizer, isWhere, stackA, stackB); var result = (objIsArr ? equalArrays : equalObjects)(object, other, equalFunc, customizer, isLoose, stackA, stackB);
stackA.pop(); stackA.pop();
stackB.pop(); stackB.pop();
@@ -2387,7 +2393,7 @@
while (++index < length) { while (++index < length) {
if ((noCustomizer && strictCompareFlags[index]) if ((noCustomizer && strictCompareFlags[index])
? values[index] !== object[props[index]] ? values[index] !== object[props[index]]
: !hasOwnProperty.call(object, props[index]) : !(props[index] in object)
) { ) {
return false; return false;
} }
@@ -2396,7 +2402,7 @@
while (++index < length) { while (++index < length) {
var key = props[index]; var key = props[index];
if (noCustomizer && strictCompareFlags[index]) { if (noCustomizer && strictCompareFlags[index]) {
var result = hasOwnProperty.call(object, key); var result = key in object;
} else { } else {
var objValue = object[key], var objValue = object[key],
srcValue = values[index]; srcValue = values[index];
@@ -2447,7 +2453,7 @@
if (isStrictComparable(value)) { if (isStrictComparable(value)) {
return function(object) { return function(object) {
return object != null && object[key] === value && hasOwnProperty.call(object, key); return object != null && object[key] === value && key in object;
}; };
} }
} }
@@ -2476,7 +2482,7 @@
function baseMatchesProperty(key, value) { function baseMatchesProperty(key, value) {
if (isStrictComparable(value)) { if (isStrictComparable(value)) {
return function(object) { return function(object) {
return object != null && object[key] === value; return object != null && object[key] === value && key in object;
}; };
} }
return function(object) { return function(object) {
@@ -3771,18 +3777,18 @@
* @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 comparing arrays. * @param {Function} [customizer] The function to customize comparing arrays.
* @param {boolean} [isWhere] Specify performing partial comparisons. * @param {boolean} [isLoose] 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 arrays are equivalent, else `false`. * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`.
*/ */
function equalArrays(array, other, equalFunc, customizer, isWhere, stackA, stackB) { function equalArrays(array, other, equalFunc, customizer, isLoose, stackA, stackB) {
var index = -1, var index = -1,
arrLength = array.length, arrLength = array.length,
othLength = other.length, othLength = other.length,
result = true; result = true;
if (arrLength != othLength && !(isWhere && othLength > arrLength)) { if (arrLength != othLength && !(isLoose && othLength > arrLength)) {
return false; return false;
} }
// Deep compare the contents, ignoring non-numeric properties. // Deep compare the contents, ignoring non-numeric properties.
@@ -3792,23 +3798,23 @@
result = undefined; result = undefined;
if (customizer) { if (customizer) {
result = isWhere result = isLoose
? customizer(othValue, arrValue, index) ? customizer(othValue, arrValue, index)
: customizer(arrValue, othValue, index); : customizer(arrValue, othValue, index);
} }
if (typeof result == 'undefined') { if (typeof result == 'undefined') {
// Recursively compare arrays (susceptible to call stack limits). // Recursively compare arrays (susceptible to call stack limits).
if (isWhere) { if (isLoose) {
var othIndex = othLength; var othIndex = othLength;
while (othIndex--) { while (othIndex--) {
othValue = other[othIndex]; othValue = other[othIndex];
result = (arrValue && arrValue === othValue) || equalFunc(arrValue, othValue, customizer, isWhere, stackA, stackB); result = (arrValue && arrValue === othValue) || equalFunc(arrValue, othValue, customizer, isLoose, stackA, stackB);
if (result) { if (result) {
break; break;
} }
} }
} else { } else {
result = (arrValue && arrValue === othValue) || equalFunc(arrValue, othValue, customizer, isWhere, stackA, stackB); result = (arrValue && arrValue === othValue) || equalFunc(arrValue, othValue, customizer, isLoose, stackA, stackB);
} }
} }
} }
@@ -3864,18 +3870,18 @@
* @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 comparing values. * @param {Function} [customizer] The function to customize comparing values.
* @param {boolean} [isWhere] Specify performing partial comparisons. * @param {boolean} [isLoose] 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, isWhere, stackA, stackB) { function equalObjects(object, other, equalFunc, customizer, isLoose, stackA, stackB) {
var objProps = keys(object), var objProps = keys(object),
objLength = objProps.length, objLength = objProps.length,
othProps = keys(other), othProps = keys(other),
othLength = othProps.length; othLength = othProps.length;
if (objLength != othLength && !isWhere) { if (objLength != othLength && !isLoose) {
return false; return false;
} }
var hasCtor, var hasCtor,
@@ -3883,7 +3889,7 @@
while (++index < objLength) { while (++index < objLength) {
var key = objProps[index], var key = objProps[index],
result = hasOwnProperty.call(other, key); result = isLoose ? key in other : hasOwnProperty.call(other, key);
if (result) { if (result) {
var objValue = object[key], var objValue = object[key],
@@ -3891,13 +3897,13 @@
result = undefined; result = undefined;
if (customizer) { if (customizer) {
result = isWhere result = isLoose
? customizer(othValue, objValue, key) ? customizer(othValue, objValue, key)
: customizer(objValue, othValue, key); : customizer(objValue, othValue, key);
} }
if (typeof result == 'undefined') { if (typeof result == 'undefined') {
// Recursively compare objects (susceptible to call stack limits). // Recursively compare objects (susceptible to call stack limits).
result = (objValue && objValue === othValue) || equalFunc(objValue, othValue, customizer, isWhere, stackA, stackB); result = (objValue && objValue === othValue) || equalFunc(objValue, othValue, customizer, isLoose, stackA, stackB);
} }
} }
if (!result) { if (!result) {
@@ -3905,7 +3911,7 @@
} }
hasCtor || (hasCtor = key == 'constructor'); hasCtor || (hasCtor = key == 'constructor');
} }
if (!hasCtor) { if (!hasCtor && !isLoose) {
var objCtor = object.constructor, var objCtor = object.constructor,
othCtor = other.constructor; othCtor = other.constructor;
@@ -8715,7 +8721,7 @@
value = source[key]; value = source[key];
if (isStrictComparable(value)) { if (isStrictComparable(value)) {
return object != null && value === object[key] && hasOwnProperty.call(object, key); return object != null && value === object[key] && key in object;
} }
} }
var values = Array(length), var values = Array(length),

View File

@@ -9634,7 +9634,7 @@
objects = [{ 'a': 1 }, { 'a': 1, 'b': 1 }, { 'a': 1, 'b': undefined }], objects = [{ 'a': 1 }, { 'a': 1, 'b': 1 }, { 'a': 1, 'b': undefined }],
actual = _.map(objects, matches); actual = _.map(objects, matches);
deepEqual(actual, [true, false, true]); deepEqual(actual, [false, false, true]);
matches = _.matchesProperty('a', { 'b': undefined }); matches = _.matchesProperty('a', { 'b': undefined });
objects = [{ 'a': { 'a': 1 } }, { 'a': { 'a': 1, 'b': 1 } }, { 'a': { 'a': 1, 'b': undefined } }]; objects = [{ 'a': { 'a': 1 } }, { 'a': { 'a': 1, 'b': 1 } }, { 'a': { 'a': 1, 'b': undefined } }];