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

View File

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