mko
mko

Reputation: 22064

Can't use Function.prototype.call directly

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.

Upvotes: 4

Views: 306

Answers (2)

Chava Geldzahler
Chava Geldzahler

Reputation: 3730

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).

Upvotes: 2

Ced
Ced

Reputation: 17337

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

Upvotes: 2

Related Questions