Choi Wonjoon
Choi Wonjoon

Reputation: 41

Why does '_.negate' uses apply with 'this' parameter in underscore.js?

I'm studying underscore.js and javascript programming. While I'm seeing the source of underscore, I can't understand some part of it.

I can't understand why it uses apply with 'this' in the _.negate, because the other parts receive 'context's and use them. Below is the source code.

https://github.com/jashkenas/underscore/blob/master/underscore.js

_.negate = function(predicate) {
    return function() {
        return !predicate.apply(this, arguments);
    };
};

The places where _.negate is used are in _.reject and _.omit. Seeing code of _.reject, it receives 'context' and use it for the context.

_.filter = _.select = function(obj, predicate, context) {
    var results = [];
    if (obj == null) return results;
    if (nativeFilter && obj.filter === nativeFilter) {
        return obj.filter(predicate, context);
    }
    each(obj, function(value, index, list) {
      if (predicate.call(context, value, index, list)) results.push(value);
    });
    return results;
};



_.reject = function(obj, predicate, context) {
    return _.filter(obj, _.negate(predicate), context);
};

Please give me some explanation. Thanks in advance.

Upvotes: 2

Views: 978

Answers (1)

mu is too short
mu is too short

Reputation: 434685

The apply call is doing two things at once:

  1. It is setting the this that will be in effect inside predicate.
  2. It is passing the anonymous function's argument list straight into predicate without having to care how many arguments are being used.

(2) should be clear enough if you know about arguments in JavaScript; arguments is an Array-like object which holds the function's current argument list, all functions in JavaScript are variadic (despite what their definition says) and arguments is how you work with the arguments when you don't know how many there are.

(1) should be clear with a quick look at how the _.negate return value is used. Inside _.filter, the predicate is invoked using call:

predicate.call(context, value, index, list)

That sets this to context inside predicate. If predicate is actually _.negate(original_predicate) then that's effectively this:

var f = function() {
    return !original_predicate.apply(this, arguments);
};
f.call(context, ...)

so original_predicate will get called like original_predicate.apply(context, arguments) and the specified this (i.e. context) will get be in effect when original_predicate executes.

If _.negate just did this:

return function(a, b, c) {
    return !predicate(a, b, c);
};

then two bad things would happen:

  1. The specified context would be lost.
  2. Only three arguments would get through. This would be sufficient for _.filter but maybe not for other uses of _.negate.

Losing track of context would break a lot of code, using Function.prototype.apply to specify the this just happens to solve the argument list problem for free.

Upvotes: 4

Related Questions