Add _.hasIn and more path helper functions.

This commit is contained in:
John-David Dalton
2015-08-18 23:29:27 -07:00
parent 356e47a6a3
commit 9859b1555f
2 changed files with 126 additions and 62 deletions

186
lodash.js
View File

@@ -1986,6 +1986,30 @@
return (index && index == length) ? object : undefined; return (index && index == length) ? object : undefined;
} }
/**
* The base implementation of `_.has` without support for deep paths.
*
* @private
* @param {Object} object The object to query.
* @param {Array|string} key The key to check.
* @returns {boolean} Returns `true` if `key` is a property, else `false`.
*/
function baseHas(object, key) {
return object != null && hasOwnProperty.call(object, key);
}
/**
* The base implementation of `_.hasIn` without support for deep paths.
*
* @private
* @param {Object} object The object to query.
* @param {Array|string} key The key to check.
* @returns {boolean} Returns `true` if `key` is a property, else `false`.
*/
function baseHasIn(object, key) {
return object != null && key in Object(object);
}
/** /**
* The base implementation of `_.isEqual` which supports partial comparisons * The base implementation of `_.isEqual` which supports partial comparisons
* and tracks traversed objects. * and tracks traversed objects.
@@ -2240,25 +2264,11 @@
* @returns {Function} Returns the new function. * @returns {Function} Returns the new function.
*/ */
function baseMatchesProperty(path, srcValue) { function baseMatchesProperty(path, srcValue) {
var isCommon = isKey(path) && isStrictComparable(srcValue),
pathKey = (path + '');
path = toPath(path);
return function(object) { return function(object) {
if (object == null) { var objValue = get(object, path);
return false; return (objValue === undefined && objValue === srcValue)
} ? hasIn(object, path)
var key = pathKey; : baseIsEqual(srcValue, objValue, undefined, true);
if (!isCommon && !(key in Object(object))) {
object = path.length == 1 ? object : baseGet(object, baseSlice(path, 0, -1));
if (object == null) {
return false;
}
key = last(path);
}
return object[key] === srcValue
? (srcValue !== undefined || (key in Object(object)))
: baseIsEqual(srcValue, object[key], undefined, true);
}; };
} }
@@ -2442,7 +2452,7 @@
} }
else if (!isKey(index, array)) { else if (!isKey(index, array)) {
var path = toPath(index), var path = toPath(index),
object = path.length == 1 ? array : baseGet(array, baseSlice(path, 0, -1)); object = parent(array, path);
if (object != null) { if (object != null) {
delete object[last(path)]; delete object[last(path)];
@@ -3763,6 +3773,33 @@
return { 'start': start, 'end': end }; return { 'start': start, 'end': end };
} }
/**
* Checks if `path` exists on `object`.
*
* @private
* @param {Object} object The object to query.
* @param {Array|string} path The path to check.
* @param {Function} hasFunc The function to check properties.
* @returns {boolean} Returns `true` if `path` exists, else `false`.
*/
function hasPath(object, path, hasFunc) {
if (object == null) {
return false;
}
object = Object(object);
var result = hasFunc(object, path);
if (!result && !isKey(path)) {
path = toPath(path);
object = parent(object, path);
if (object != null) {
path = last(path);
result = hasFunc(object, path);
}
}
return result || (isLength(object && object.length) && isIndex(path, object.length) &&
(isArray(object) || isArguments(object) || isString(object)));
}
/** /**
* Initializes an array clone. * Initializes an array clone.
* *
@@ -3866,9 +3903,9 @@
* @returns {*} Returns the result of the invoked method. * @returns {*} Returns the result of the invoked method.
*/ */
function invokePath(object, path, args) { function invokePath(object, path, args) {
if (object != null && !isKey(path, object)) { if (!isKey(path, object)) {
path = toPath(path); path = toPath(path);
object = path.length == 1 ? object : baseGet(object, baseSlice(path, 0, -1)); object = parent(object, path);
path = last(path); path = last(path);
} }
var func = object == null ? object : object[path]; var func = object == null ? object : object[path];
@@ -3933,15 +3970,12 @@
* @returns {boolean} Returns `true` if `value` is a property name, else `false`. * @returns {boolean} Returns `true` if `value` is a property name, else `false`.
*/ */
function isKey(value, object) { function isKey(value, object) {
var type = typeof value; if (typeof value == 'number') {
if ((type == 'string' && reIsPlainProp.test(value)) || type == 'number') {
return true; return true;
} }
if (isArray(value)) { return !isArray(value) &&
return false; (reIsPlainProp.test(value) || !reIsDeepProp.test(value) ||
} (object != null && value in Object(object)));
var result = !reIsDeepProp.test(value);
return result || (object != null && value in Object(object));
} }
/** /**
@@ -4091,6 +4125,18 @@
return objValue === undefined ? srcValue : objValue; return objValue === undefined ? srcValue : objValue;
} }
/**
* Gets the parent value at `path` of `object`.
*
* @private
* @param {Object} object The object to query.
* @param {Array} path The path to get the parent value of.
* @returns {*} Returns the parent value.
*/
function parent(object, path) {
return path.length == 1 ? object : get(object, baseSlice(path, 0, -1));
}
/** /**
* Reorder `array` according to the specified indexes where the element at * Reorder `array` according to the specified indexes where the element at
* the first index is assigned as the first element, the element at * the first index is assigned as the first element, the element at
@@ -8930,17 +8976,18 @@
} }
/** /**
* Checks if `path` is a direct property. * Checks if `path` is a direct property of `object`.
* *
* @static * @static
* @memberOf _ * @memberOf _
* @category Object * @category Object
* @param {Object} object The object to query. * @param {Object} object The object to query.
* @param {Array|string} path The path to check. * @param {Array|string} path The path to check.
* @returns {boolean} Returns `true` if `path` is a direct property, else `false`. * @returns {boolean} Returns `true` if `path` exists, else `false`.
* @example * @example
* *
* var object = { 'a': { 'b': { 'c': 3 } } }; * var object = { 'a': { 'b': { 'c': 3 } } },
* other = _.create({ 'a': _.create({ 'b': _.create({ 'c': 3 }) }) });
* *
* _.has(object, 'a'); * _.has(object, 'a');
* // => true * // => true
@@ -8950,23 +8997,41 @@
* *
* _.has(object, ['a', 'b', 'c']); * _.has(object, ['a', 'b', 'c']);
* // => true * // => true
*
* _.has(other, 'a');
* // => false
*/ */
function has(object, path) { function has(object, path) {
if (object == null) { return hasPath(object, path, baseHas);
return false; }
}
var result = hasOwnProperty.call(object, path); /**
if (!result && !isKey(path)) { * Checks if `path` is a direct or inherited property of `object`.
path = toPath(path); *
object = path.length == 1 ? object : baseGet(object, baseSlice(path, 0, -1)); * @static
if (object == null) { * @memberOf _
return false; * @category Object
} * @param {Object} object The object to query.
path = last(path); * @param {Array|string} path The path to check.
result = hasOwnProperty.call(object, path); * @returns {boolean} Returns `true` if `path` exists, else `false`.
} * @example
return result || (isLength(object.length) && isIndex(path, object.length) && *
(isArray(object) || isArguments(object) || isString(object))); * var object = _.create({ 'a': _.create({ 'b': _.create({ 'c': 3 }) }) });
*
* _.hasIn(object, 'a');
* // => true
*
* _.hasIn(object, 'a.b.c');
* // => true
*
* _.hasIn(object, ['a', 'b', 'c']);
* // => true
*
* _.hasIn(object, 'b');
* // => false
*/
function hasIn(object, path) {
return hasPath(object, path, baseHasIn);
} }
/** /**
@@ -9387,23 +9452,22 @@
* // => 'default' * // => 'default'
*/ */
function result(object, path, defaultValue) { function result(object, path, defaultValue) {
var isPath = !isKey(path, object), if (!isKey(path, object)) {
result = (isPath || object == null) ? undefined : object[path]; path = toPath(path);
var result = get(object, path);
object = parent(object, path);
} else {
result = object == null ? undefined : object[path];
}
if (result === undefined) { if (result === undefined) {
if (object != null && isPath) { result = defaultValue;
path = toPath(path);
object = path.length == 1 ? object : baseGet(object, baseSlice(path, 0, -1));
result = object == null ? undefined : object[last(path)];
}
result = result === undefined ? defaultValue : result;
} }
return isFunction(result) ? result.call(object) : result; return isFunction(result) ? result.call(object) : result;
} }
/** /**
* Sets the property value of `path` on `object`. If a portion of `path` * Sets the value at `path` of `object`. If a portion of `path` doesn't
* doesn't exist it's created. * exist it's created.
* *
* @static * @static
* @memberOf _ * @memberOf _
@@ -10663,8 +10727,8 @@
} }
/** /**
* Creates a function that compares the property value of `path` on a given * Creates a function that compares the value at `path` on a given object
* object to `srcValue`. * to `srcValue`.
* *
* **Note:** This method supports comparing arrays, booleans, `Date` objects, * **Note:** This method supports comparing arrays, booleans, `Date` objects,
* numbers, `Object` objects, regexes, and strings. * numbers, `Object` objects, regexes, and strings.
@@ -10854,8 +10918,7 @@
} }
/** /**
* Creates a function that returns the property value at `path` on a * Creates a function that returns the value at `path` on a given object.
* given object.
* *
* @static * @static
* @memberOf _ * @memberOf _
@@ -11448,6 +11511,7 @@
lodash.gt = gt; lodash.gt = gt;
lodash.gte = gte; lodash.gte = gte;
lodash.has = has; lodash.has = has;
lodash.hasIn = hasIn;
lodash.identity = identity; lodash.identity = identity;
lodash.includes = includes; lodash.includes = includes;
lodash.indexOf = indexOf; lodash.indexOf = indexOf;

View File

@@ -17266,7 +17266,7 @@
var acceptFalsey = _.difference(allMethods, rejectFalsey); var acceptFalsey = _.difference(allMethods, rejectFalsey);
test('should accept falsey arguments', 222, function() { test('should accept falsey arguments', 223, function() {
var emptyArrays = _.map(falsey, _.constant([])); var emptyArrays = _.map(falsey, _.constant([]));
_.each(acceptFalsey, function(methodName) { _.each(acceptFalsey, function(methodName) {