Ensure _.where works correctly for nested properties and give indicator arguments more meaningful names.

Former-commit-id: c35e2817125cd852a66066ccdef44bcc40c93e61
This commit is contained in:
John-David Dalton
2013-01-26 23:47:47 -08:00
parent 17fc3c2317
commit 23c3ba6ad7
7 changed files with 204 additions and 191 deletions

View File

@@ -556,10 +556,10 @@
* @param {Function|String} func The function to bind or the method name.
* @param {Mixed} [thisArg] The `this` binding of `func`.
* @param {Array} partialArgs An array of arguments to be partially applied.
* @param {Object} [right] Used to indicate partially applying arguments from the right.
* @param {Object} [rightIndicator] Used to indicate partially applying arguments from the right.
* @returns {Function} Returns the new bound function.
*/
function createBound(func, thisArg, partialArgs, right) {
function createBound(func, thisArg, partialArgs, rightIndicator) {
var isFunc = isFunction(func),
isPartial = !partialArgs,
key = thisArg;
@@ -583,7 +583,7 @@
}
if (partialArgs.length) {
args = args.length
? (args = slice(args), right ? args.concat(partialArgs) : partialArgs.concat(args))
? (args = slice(args), rightIndicator ? args.concat(partialArgs) : partialArgs.concat(args))
: partialArgs;
}
if (this instanceof bound) {
@@ -630,7 +630,7 @@
var length = props.length,
result = false;
while (length--) {
if (!(result = isEqual(object[props[length]], func[props[length]]))) {
if (!(result = isEqual(object[props[length]], func[props[length]], indicatorObject))) {
break;
}
}
@@ -1031,10 +1031,9 @@
* @memberOf _
* @category Objects
* @param {Mixed} value The value to clone.
* @param- {Object} [deep] Internally used to indicate performing a deep clone.
* @param- {Object} [deepIndicator] Internally used to indicate performing a deep clone.
* @param- {Array} [stackA=[]] Internally used to track traversed source objects.
* @param- {Array} [stackB=[]] Internally used to associate clones with their
* source counterparts.
* @param- {Array} [stackB=[]] Internally used to associate clones with their source counterparts.
* @returns {Mixed} Returns the cloned `value`.
* @example
*
@@ -1050,10 +1049,10 @@
* shallow == stooges;
* // => false
*/
function clone(value, deep, stackA, stackB) {
function clone(value, deepIndicator, stackA, stackB) {
// allows working with "Collections" methods without using their `callback`
// arguments, `index|key` and `collection`
deep = deep == indicatorObject;
deepIndicator = deepIndicator == indicatorObject;
// inspect [[Class]]
var isObj = isObject(value);
@@ -1065,7 +1064,7 @@
var isArr = isArray(value);
}
// shallow clone
if (!isObj || !deep) {
if (!isObj || !deepIndicator) {
return isObj
? (isArr ? slice(value) : assign({}, value))
: value;
@@ -1340,6 +1339,8 @@
* @category Objects
* @param {Mixed} a The value to compare.
* @param {Mixed} b The other value to compare.
* @param- {Object} [whereIndicator] Internally used to indicate that when
* comparing objects, `a` has at least the properties of `b`.
* @param- {Object} [stackA=[]] Internally used track traversed `a` objects.
* @param- {Object} [stackB=[]] Internally used track traversed `b` objects.
* @returns {Boolean} Returns `true`, if the values are equvalent, else `false`.
@@ -1354,27 +1355,32 @@
* _.isEqual(moe, clone);
* // => true
*/
function isEqual(a, b, stackA, stackB) {
function isEqual(a, b, whereIndicator, stackA, stackB) {
// exit early for identical values
if (a === b) {
// treat `+0` vs. `-0` as not equal
return a !== 0 || (1 / a == 1 / b);
}
// exit early for unlike `null` or `undefined` values
if (a == null || b == null) {
var type = typeof a,
otherType = typeof b;
// exit early for unlike primitive values
if (a === a &&
(!a || (type != 'function' && type != 'object')) &&
(!b || (otherType != 'function' && otherType != 'object'))) {
return false;
}
// compare [[Class]] names
var className = toString.call(a),
otherName = toString.call(b);
otherClass = toString.call(b);
if (className == argsClass) {
className = objectClass;
}
if (otherName == argsClass) {
otherName = objectClass;
if (otherClass == argsClass) {
otherClass = objectClass;
}
if (className != otherName) {
if (className != otherClass) {
return false;
}
switch (className) {
@@ -1401,7 +1407,7 @@
if (!isArr) {
// unwrap any `lodash` wrapped values
if (a.__wrapped__ || b.__wrapped__) {
return isEqual(a.__wrapped__ || a, b.__wrapped__ || b);
return isEqual(a.__wrapped__ || a, b.__wrapped__ || b, whereIndicator);
}
// exit for functions and DOM nodes
if (className != objectClass || (noNodeClass && (isNode(a) || isNode(b)))) {
@@ -1441,13 +1447,13 @@
// recursively compare objects and arrays (susceptible to call stack limits)
if (isArr) {
// compare lengths to determine if a deep comparison is necessary
size = a.length;
result = size == b.length;
size = b.length;
result = whereIndicator == indicatorObject || size == a.length;
if (result) {
// deep compare the contents, ignoring non-numeric properties
while (size--) {
if (!(result = isEqual(a[size], b[size], stackA, stackB))) {
if (!(result = isEqual(a[size], b[size], whereIndicator, stackA, stackB))) {
break;
}
}
@@ -1456,20 +1462,20 @@
}
// deep compare objects using `forIn`, instead of `forOwn`, to avoid `Object.keys`
// which, in this case, is more costly
forIn(a, function(value, key, a) {
if (hasOwnProperty.call(a, key)) {
forIn(b, function(value, key, b) {
if (hasOwnProperty.call(b, key)) {
// count the number of properties.
size++;
// deep compare each property value.
return (result = hasOwnProperty.call(b, key) && isEqual(value, b[key], stackA, stackB));
return (result = hasOwnProperty.call(a, key) && isEqual(a[key], value, whereIndicator, stackA, stackB));
}
});
if (result) {
if (result && whereIndicator != indicatorObject) {
// ensure both objects have the same number of properties
forIn(b, function(value, key, b) {
if (hasOwnProperty.call(b, key)) {
// `size` will be `-1` if `b` has more properties than `a`
forIn(a, function(value, key, a) {
if (hasOwnProperty.call(a, key)) {
// `size` will be `-1` if `a` has more properties than `b`
return (result = --size > -1);
}
});
@@ -1730,8 +1736,8 @@
* @param {Object} [source1, source2, ...] The source objects.
* @param {Function} [callback] The function called for each property to merge.
* @param {Mixed} [thisArg] The `this` binding of `callback`.
* @param- {Object} [indicator] Internally used to indicate that the `stack`
* argument is an array of traversed objects instead of another source object.
* @param- {Object} [deepIndicator] Internally used to indicate that `stackA`
* and `stackB` are arrays of traversed objects instead of source objects.
* @param- {Array} [stackA=[]] Internally used to track traversed source objects.
* @param- {Array} [stackB=[]] Internally used to associate values with their
* source counterparts.
@@ -1755,7 +1761,7 @@
* _.merge(names, ages);
* // => { 'stooges': [{ 'name': 'moe', 'age': 40 }, { 'name': 'larry', 'age': 50 }] }
*/
function merge(object, source, indicator) {
function merge(object, source, deepIndicator) {
var args = arguments,
index = 0,
length = 2;
@@ -1763,7 +1769,7 @@
if (!isObject(object)) {
return object;
}
if (indicator === indicatorObject) {
if (deepIndicator === indicatorObject) {
var callback = args[3],
stackA = args[4],
stackB = args[5];
@@ -1774,7 +1780,7 @@
// allows working with `_.reduce` and `_.reduceRight` without
// using their `callback` arguments, `index|key` and `collection`
if (typeof indicator != 'number') {
if (typeof deepIndicator != 'number') {
length = args.length;
callback = typeof (callback = args[length - 2]) == 'function'
? createCallback(callback, args[--length])
@@ -2766,7 +2772,7 @@
/**
* Examines each element in a `collection`, returning an array of all elements
* that contain the given `properties`.
* that have the given `properties`.
*
* @static
* @memberOf _