2

The following code is the source code of negate function in lodash. As we can see that when the length of parameters less than 4, it uses switch-case instead of using apply directly. What magic about this code? Does it make the performance better? And why the split point is 4?

function negate(predicate) {
  if (typeof predicate != 'function') {
    throw new TypeError(FUNC_ERROR_TEXT);
  }
  return function() {
    var args = arguments;
    switch (args.length) {
      case 0: return !predicate.call(this);
      case 1: return !predicate.call(this, args[0]);
      case 2: return !predicate.call(this, args[0], args[1]);
      case 3: return !predicate.call(this, args[0], args[1], args[2]);
    }
    return !predicate.apply(this, args);
  };
}
Jasin Yip
  • 308
  • 1
  • 12
  • Not sure but I guess `switch` performs better than `if..else` ladder. Plus, less typing. – Rajesh Dec 07 '17 at 10:36
  • 2
    OP's question is not why it uses `switch` instead of `if..else`. The question is why there is a conditional at all. – JanS Dec 07 '17 at 10:51
  • The split point is 4, I guess, because you shouldn' t have function that takes more than 3 parameters in oop (and also it would be quite painful and boring to hard code more cases by hand, unless you like to repeat yourself). And they probably did a benchmark too, `apply` is probably slower than the `call` method. – romain-aga Dec 07 '17 at 10:56

1 Answers1

0

Sry, but my crystal ball is still in repair. So I can only give you my best guess about the intentions of the author.

I think the point here is not about the switch but rather to enable the optimizer to convert this whole construct into a a single one of these paths, assuming the function will be called with a consistent set of arguments.

basically optimizing this whole constuct away and even inlining the function body of the predicate into something like this pseudocode.
assuming the function was always called with 2 arguments

function(){
    //a gate to ensure the optimization is still correct/applicable
    //probably more complex than this
    if(arguments.length === 2){          
        //assign predicate function args
        var value = arguments[0], index = arguments[1];
        [function body of predicate]
    }else{
        [de-optimize]
        //execute the de-optimized version of this function
    }
}

Why 0..3 arguments? imo, these are the most common cases. And the default case is only there for completeness and (again, my opinion) should never/rarely been hit.

Another point may be, that Function#call() is in my experience a bit faster than Function#apply(). Rarely enough to even think about it, but this is a lib that will be used by other libraries, so every performance issue may increase exponetially.

Thomas
  • 11,958
  • 1
  • 14
  • 23