Skip to content

Commit

Permalink
Merge pull request #2620 from captbaritone/deep-invoke
Browse files Browse the repository at this point in the history
Make _.invoke support deep method paths
  • Loading branch information
captbaritone committed Nov 4, 2016
2 parents 817bcee + 97cfcbc commit c2e2eff
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 15 deletions.
34 changes: 33 additions & 1 deletion test/collections.js
Expand Up @@ -451,7 +451,7 @@


QUnit.test('invoke', function(assert) {
assert.expect(5);
assert.expect(13);
var list = [[5, 1, 7], [3, 2, 1]];
var result = _.invoke(list, 'sort');
assert.deepEqual(result[0], [1, 5, 7], 'first array sorted');
Expand All @@ -468,6 +468,38 @@
assert.raises(function() {
_.invoke([{a: 1}], 'a');
}, TypeError, 'throws for non-functions');

var getFoo = _.constant('foo');
var getThis = function() { return this; };
var item = {
a: {
b: getFoo,
c: getThis,
d: null
},
e: getFoo,
f: getThis,
g: function() {
return {
h: getFoo
};
}
};
var arr = [item];
assert.deepEqual(_.invoke(arr, ['a', 'b']), ['foo'], 'supports deep method access via an array syntax');
assert.deepEqual(_.invoke(arr, ['a', 'c']), [item.a], 'executes deep methods on their direct parent');
assert.deepEqual(_.invoke(arr, ['a', 'd', 'z']), [void 0], 'does not try to access attributes of non-objects');
assert.deepEqual(_.invoke(arr, ['a', 'd']), [null], 'handles deep null values');
assert.deepEqual(_.invoke(arr, ['e']), ['foo'], 'handles path arrays of length one');
assert.deepEqual(_.invoke(arr, ['f']), [item], 'correct uses parent context with shallow array syntax');
assert.deepEqual(_.invoke(arr, ['g', 'h']), [void 0], 'does not execute intermediate functions');

arr = [{
a: function() { return 'foo'; }
}, {
a: function() { return 'bar'; }
}];
assert.deepEqual(_.invoke(arr, 'a'), ['foo', 'bar'], 'can handle different methods on subsequent objects');
});

QUnit.test('invoke w/ function reference', function(assert) {
Expand Down
41 changes: 27 additions & 14 deletions underscore.js
Expand Up @@ -146,6 +146,15 @@
};
};

var deepGet = function(obj, path) {
var length = path.length;
for (var i = 0; i < length; i++) {
if (obj == null) return void 0;
obj = obj[path[i]];
}
return length ? obj : void 0;
};

// Helper for collection methods to determine whether a collection
// should be iterated as an array or as an object.
// Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
Expand Down Expand Up @@ -282,11 +291,24 @@
};

// Invoke a method (with arguments) on every item in a collection.
_.invoke = restArgs(function(obj, method, args) {
var isFunc = _.isFunction(method);
return _.map(obj, function(value) {
var func = isFunc ? method : value[method];
return func == null ? func : func.apply(value, args);
_.invoke = restArgs(function(obj, path, args) {
var contextPath, func;
if (_.isFunction(path)) {
func = path;
} else if (_.isArray(path)) {
contextPath = path.slice(0, -1);
path = path[path.length - 1];
}
return _.map(obj, function(context) {
var method = func;
if (!method) {
if (contextPath && contextPath.length) {
context = deepGet(context, contextPath);
}
if (context == null) return void 0;
method = context[path];
}
return method == null ? method : method.apply(context, args);
});
});

Expand Down Expand Up @@ -1382,15 +1404,6 @@

_.noop = function(){};

var deepGet = function(obj, path) {
var length = path.length;
for (var i = 0; i < length; i++) {
if (obj == null) return void 0;
obj = obj[path[i]];
}
return length ? obj : void 0;
};

_.property = function(path) {
if (!_.isArray(path)) {
return shallowProperty(path);
Expand Down

0 comments on commit c2e2eff

Please sign in to comment.