4
function f(a) { return a}

f(1) // => 1

f.call(null, 1) // => 1

Function.prototype.call(f, null, 1) // => undefined

Why the last line return undefined, I thought they are the same.

mko
  • 21,334
  • 49
  • 130
  • 191
  • 1
    `Function.prototype.call.call(f, null, 1)` What you're doing right now is `var fp = Function.prototype; fp.call(f, null, 1)` and `fp.call(...)` ain't the same as `f.call(...)`. These are simply different functions. – Thomas Jun 12 '17 at 01:31
  • See https://stackoverflow.com/questions/31074664/javascript-function-prototype-call – Chava Geldzahler Jun 12 '17 at 01:33
  • thank you guys for the quick rely, this is a mind stretcher – mko Jun 12 '17 at 01:55
  • @mko I'd appreciate if you checked my answer, I don't think the current answer, nor the one he linked to makes a good job of answering the question. That's just my opinion though. – Ced Jun 12 '17 at 03:04
  • @mko I updated my answer to give you a digestible explanation of the difference between `Function.prototype.call` and `Function.prototype.call.call` based on the specification of how Function.prototype.call is really supposed to work. Hope it helps... – Chava Geldzahler Jun 12 '17 at 18:30

2 Answers2

2

These will be the same:

function f(a) { return a}

console.log(f(1)); // => 1

console.log(f.call(null, 1)); // => 1

console.log(Function.prototype.call.call(f, null, 1)); // => 1

Notice the additional .call in the last statement.

And here's the explanation:

Function.prototype.call

According to the spec, Function.prototype.call returns an abstract operation Call(func, thisArg, argList).

Therefore, f.call(null, 1) will return the abstract operation Call(f, null, 1) where f is the function being called, null is the context from which it is called, and 1 is the argument passed to f. This will give you the desired output.

Based on that, Function.prototype.call(f, null, 1) will result in the abstract operation Call(Function.prototype, f, null, 1) where Function.prototype is the function being called, f is the context, and null and 1 are the arguments passed to Function.prototype. Of course this will not work as intended.

Function.prototype.call.call

However, Function.prototype.call.call(f, null, 1) will return the abstract call operation Call(Function.prototype.call, f, null, 1), where Function.prototype.call is the function to be called, f is the context from which it is called, and null and 1 are passed as arguments. So what would that look like? Well, since f is the context and call is the function being invoked with (null,1), the end result is identical to: f.call(null, 1).

Chava Geldzahler
  • 3,605
  • 1
  • 18
  • 32
2

Let's start with this:

function fn() { console.log(this); }
fn.a = function(){console.log(this)}
fn.a() // function fn() { console.log(this); }

So let's dig deeper and try to implement a fake call function:

  Function.prototype.fakeCall = function () {
    // console.log(this)
    // taking the arguments after the first one
    let arr = Array.prototype.slice.call(arguments, 1);
    // on the first run this will be Function.fakeCall but on the second pass it will be the first argument (the a function)
    this.apply(arguments[0], arr);
  }

  function a(ar){ console.log(ar + this.name) };
  let obj = {name : "thierry"};
 // a.fakeCall( obj, 'hi ')

Function.fakeCall.fakeCall(a, obj, 'hi ');

Thus when we do this: Function.prototype.fakeCall.fakeCall(a, obj, 'hi ')

what happens is, on the first run we have:

  1. arr = [ obj, 'hi ']
  2. this = Function.fakeCall

so we end up with Function.fakeCall.apply(a, [ obj, 'hi ']);

Then on the second run we have

  1. arr = ['hi']
  2. this = a

so we end up with a.apply(obj, ['hi']) which is the same as a.call(obj, 'hi');

If however we did Function.fakeCall(a, obj, 'hi '); On the first run we would have this = Function and that won't work. It will throw an error in this case, in your case it just returns undefined. That is easily implementable with a try-catch.

  Function.prototype.fakeCall = function () {
    let arr = Array.prototype.slice.call(arguments, 1);
    try{
       return this.apply(arguments[0], arr);
    }catch(e){}
  }

  function a(ar){ return ar + this.name };
  let obj = {name : "thierry"};
  console.log(Function.fakeCall(a, obj, 'hi ')); // undefined

  console.log(Function.fakeCall.fakeCall(a, obj, 'hi ')); // hi thierry
Ced
  • 15,847
  • 14
  • 87
  • 146
  • your style of explanation teach me more about the underlying mechanism. thank you – mko Jun 12 '17 at 07:59