'Calling a function directly vs using the apply/call method

In the following code to generate a not function:

function not(func) {
    return (...args) => !func(...args);
}

const even = x => x%2 === 0;
const odd = not(even);


let a = [1,2,3,4,5];
console.log(a, a.map(odd));

Is there any reason to use func(...args) vs func.call(this, ...args)? What might be a use case where the second would be a better option than the first (if ever)? And finally, is func.bind(this)(...args) the same as func.call(this, ...args)?

Another example would be the difference between the two ways of invoking it:

function compose(f, g) {
    return function(...args) {
        return f(g(...args));                       // first
        return f.call(this, g.apply(this, args));   // second
        return f.bind(this)(g.bind(this)(...args)); // third

    }
}
console.log(compose(x=>x*x, (x,y)=>x+y)(2,3));

Obviously the first one would be preferred if they're all essentially the same.



Solution 1:[1]

Because not is using arrow notation to return its new function, if it contained the this keyword its value would be whatever this is when not is called, rather than whatever this is when the returned function is called.

The value of this could be conserved if not avoids using arrow notation, because functions created by arrow notation don't have their own this or arguments:

function not(func) {
    return function (...args) {
        return !func.apply(this, args);
    }
}

Avoiding arrow notation also means you could pass through arguments directly instead of using a rest parameter to gather them as an Array:

function not(func) {
    return function () {
        return !func.apply(this, arguments);
    }
}

Maintaining the value of this is only important if you expect your not function might be applied to functions that care about their this value. But it's quite easy to maintain, and unless your use case requires something different there should be no harm in passing it through like this.


To answer your second question, no func.bind(this)(...args) is not quite the same as func.call(this, ...args). Though the difference doesn't really matter so much if you're not saving the function returned by func.bind(this).

Function.prototype.bind creates a new version of a function which always has the same value for this regardless of how that function is invoked. Function.prototype.call, on the other hand, only invokes a function in a particular way, it doesn't also create a new version of it first.

Solution 2:[2]

It makes a difference, if you use a prototype from a method like Set#has.

In this case this has to be preserved for the call, or better with apply, because of having an array of arguments.

function not(func) {
    return function(...args) { return !func.apply(this, args); };
    //              or with spreading !func.call(this, ...args);
}

const hasNot = not(Set.prototype.has);

let a = [1, 2, 3, 4, 5];
console.log(a.map(hasNot, new Set([1, 2, 3])));

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Mark Hanna
Solution 2 Nina Scholz