2

I'm trying to understand underscore's invoke() function, but I'm having trouble in a few areas. Here's the code from the annotated source:

  _.invoke = function(obj, method) {
    var args = slice.call(arguments, 2);
    var isFunc = _.isFunction(method);
    return _.map(obj, function(value) {
      var func = isFunc ? method : value[method];
      return func == null ? func : func.apply(value, args);
    });
  };

In this example:

_.invoke([[5, 1, 7], [3, 2, 1]], 'sort');
=> [[1, 5, 7], [1, 2, 3]]

I understand what is going on; however, in the source code, I don't understand the function of args. In what kind of situations would additional arguments need to be used?

I'm also new to programming, and I'm struggling with how apply is being used here. I'm guessing it's because I don't fully understand the use of invoke, namely the use of args. Still, in looking at examples provided in Mozilla's documentation, one example used Math.max.apply(*null*, numbers), so why is it being applied to value in the source code?

Note: I've read many articles on it including this, this, and this, and several other videos, but am still struggling.

Community
  • 1
  • 1
TheRealFakeNews
  • 7,512
  • 16
  • 73
  • 114
  • 1
    [The lodash docs](https://lodash.com/docs#invoke) have a better example that might explain it better – Andy Apr 01 '16 at 16:50
  • You should be doing `_.invoke(arrOfArrays, 'sort',function(a, b){ return a-b;})` – Bergi Apr 02 '16 at 11:09

1 Answers1

7

If you check Underscore invoke docs, you see this:

Calls the method named by methodName on each value in the list. Any extra arguments passed to invoke will be forwarded on to the method invocation.

You see? All that magic with apply and args is used so you could fire that method in invoke with some arguments.

Whats the function of args?

Take a look at join array method - it joins elements into a string, and if we pass something - it would be used as a separator.

var things = ['apple', 'banana', 'mango'];

things.join('#') // 'apple#banana#mango'

So, join can take arguments. Lets use it with invoke now.

var manyThings = [ 
  ['apple', 'banana', 'mango'],
  ['pepsi', 'fanta', 'sprite'],
  ['bear', 'wolf', 'parrot'] 
];

// Pass '#' as a third argument - is like join('#')
console.log(_.invoke(arr, 'join', '#'));

// ["apple#banana#mango", "pepsi#fanta#sprite", "bear#wolf#parrot"]

We passed '#' to that join method! That's the situation when we use that additional arguments.

How it works?

var args = slice.call(arguments, 2);

We store all arguments passed to invoke, starting from third (first is a list, second is a method name). We store '#' in our manyThings case.

Every invoke argument we pass after methodName becomes arguments for this methodName function.

_.invoke(obj, 'methodName', '#', 2, false, '--')
// It's like do obj.methodName('#', 2, false, '--')

Function of args - to store passed arguments to use it in the method.

Why we need apply?

When we have all that arguments, we are ready to pass them to method we have here:

var func = isFunc ? method : value[method];

You see? We took value[method] function. Somewhere deep inside this function use this. So to be able to fire that function on the right object with right this (manyThings array), we need apply. And we pass args as a second parameter to apply (in our case it's '#').

This how it works, i hope you have better understanding now.

  • I really appreciate the explanation @Alexander. I am having trouble understanding the 'value[method]' portion of the ternary operator. Can you expand on this snippet of the code? – Matt Fernandez Mar 24 '17 at 16:22
  • 2
    We can pass `methodName` or function. So in ternary we check - if we pass function, use it as is. If we pass not function - like string 'sort', we assume it's a method name, so we pick it from object `value`. And and this object - from current iteration by collection we pass to invoke initially. – Alexander Doroshenko Mar 24 '17 at 22:23