NewAtLearningThis
NewAtLearningThis

Reputation: 239

Writing my version of _.reject in Underscore JS

  // Return all elements of an array that pass a truth test.
  _.filter = function(collection, test) {
    var result = []; //make a result variable 
    //iterate over the collection array using _.each
    _.each(collection, function(value) {
    //if conditions for test(element) = positive
      if (test(value)) {
        //push element into result
        result.push(value);
      }
    });
    return result;
  };

Hi, I'm working on a rewriting the function of _.filter & _.reject from the Underscore JS library. I still get a bit confused about callback functions particularly when there are so many in the same function. I need some clarification about my thoughts and logic here; particularly about ._reject. I am reusing my _.filter function and implenting it in my _.reject func.

 _.reject = function(collection, test) {
    return _.filter(collection, function(value) {
      return !test(value);
    })
  };

When I have function(value) as a parameter for filter and defined it; is that replacing my test value with that functions callback? Particularly in this line - if (test(value)) {... does that get replaced with my callback in this case if(function(value) {return !test(value)}) Also, if I omit the return command from my _.reject function for _.filter; am I just returning the function itself? Sorry - I know it's a bit bizarre since my code works as far as I know but it was really more of an effort of trying things out then actually understanding it logically. I just get very confused on what happens when I calling the callback function in filter from my reject function

Upvotes: 0

Views: 393

Answers (1)

mattbasta
mattbasta

Reputation: 13709

In JavaScript, everything is an object—including functions. When you write a function expression (as they're called) like this:

const foo = function() {
  console.log('This function prints this message!');
};

foo now contains a reference to the function that prints the message. We can call it like this:

foo();

…which runs the code and prints the message. You can pass foo around like any other value, including as a parameter to other functions. Think of it like saving a set of instructions for doing something: you're telling the computer "foo contains the code that, when run, prints a message."

In your example, the function expression that you're passing as the second argument to _.filter is an object:

function(value) {
  return !test(value);
}

You're saying "Pass this function—which takes a value, calls test with it, negates the result, and returns it—to my _.filter function." _.filter knows to call that function to filter out values.

Programming is full of these sorts of patterns: the goal isn't to look at what's happening all the way down, it's to understand that _.filter accepts a second parameter that's a function that says "If you give me a value, I'll tell you whether to keep it." And in your code, you're passing it a function that does just that—but it also calls another function (test) that your user passed in. Chains of function calls can be long: hundreds or thousands of functions calling other functions.

In your case, it looks like your code is correct. _.filter says "Give me an array of data and a function. I'll call that function for each value in the array, and it'll return me whether to keep it." You're making a function that says "Give me an array of data and a function. I'll call that function for each value in the array, and it'll return me whether not to keep it." To do this, you're just inverting what _.filter wants to know: instead of whether to keep a record, you want to know whether to eliminate it.

So your code gives the user's array and a function to _.filter just like it wants. The function gets called with each element of the array. _.filter wants to know whether to keep the item. You're going to call test with the item that _.filter asks you about. But if the user's function returns true, we want to eliminate the element, rather than keep it, so we negate that (with !) and return the value. You're then returning the result of _.filter.

To clarify about return: the return keyword tells the function calling your function what you want to "give back". So when you return !test(value), you're telling whatever called your function the value of !test(value) (in this case, that's _.filter). If you didn't use return, you wouldn't be giving anything back to the code that called your function. If the user wants to know what the array looks like after the elements have been rejected, and your function didn't return anything, then your function isn't very useful!

Upvotes: 1

Related Questions