Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Function.prototype.call unexpected behavior when assigned to a variable

Tags:

javascript

The following code calls console.log prints "hello":

console.log.call(console, "hello")

However, the code below throws TypeError:

x = console.log.call
x(console, "hello")

throws:

Uncaught TypeError: x is not a function
    at <anonymous>:1:1

Can anyone explain this weird scenario?

(Of course it's the same for both call and apply)

like image 799
Eliran Pe'er Avatar asked Feb 25 '18 21:02

Eliran Pe'er


People also ask

What is function prototype call?

The call() allows for a function/method belonging to one object to be assigned and called for a different object. call() provides a new value of this to the function/method. With call() , you can write a method once and then inherit it in another object, without having to rewrite the method for the new object.

What is the difference between using call () and apply () to invoke a function with multiple arguments?

The Difference Between call() and apply() The difference is: The call() method takes arguments separately. The apply() method takes arguments as an array. The apply() method is very handy if you want to use an array instead of an argument list.

What is the fundamental difference between call () and apply () method?

Difference between call() and apply() method: The only difference is call() method takes the arguments separated by comma while apply() method takes the array of arguments. Example 1: This example uses call() method to call a function.


2 Answers

.call gets the function to call from its this parameter.

You're calling it via x with no this parameter, so it has no function to call (or rather, it tries to call window) and gives an error.

You need to bind your x variable to the log function:

x = console.log.call.bind(console.log);

Bonus: .call comes from Function.prototype, and is the same no matter how you access it. Therefore, Function.call.bind(console.log) also works (because Function is a function and therefore has .call). As does Date.call.

like image 87
SLaks Avatar answered Sep 24 '22 12:09

SLaks


Note: I will use apply instead of call in my answer just because the wording/reading is a bit less confusing but the same answer stands for call.

You can imagine apply looking something like this:

Function.prototype.apply = function apply(context, rest) {
  // `this` in here is the function object on which we call `apply` as a method
  // we then invoke whatever is bound to `this` (it should be the function that was "applied")
  // and change its context and pass the rest of the arguments
  // Note: I'm using `call` since we don't have access to native function  code that can call a function with an overwritten context
  this.call(context, ...rest)
}

When you call the apply function (or any function for that matter) as a method on a function object (functions are first-class objects), this within it gets bound to the function (object) on which you called apply (this is one of the rules on how context is bound in JS when a function is called as a method of an object)

Function.prototype.apply = function apply(context, rest) {
  this.call(context, ...rest)
  // `this` is the function that `call` invokes
  // in the example bellow, `this` is `console.log`
  // so this function will do `console.log.call(console, 'example')`
}

console.log.apply(console, ['example'])
// ^^^^^^^^ console.log is the context because we are calling `apply` on it, with the dot (.) notation

However, when you store the apply function into a variable and invoke it then, then the rule for this binding is it to be undefined (in strict mode or the global window otherwise) and thus there's no function to call under the hood.

Function.prototype.apply = function apply(context, rest) {
  this.call(context, ...rest)
  // using the example bellow
  // `this` is `undefined` now because `apply` was called like a regular function, not a method
  // which means the code bellow does `undefined.call(console, 'example')`
}


// calling a function as a normal function (not a method via the dot notation), makes `this` be `undefined`
// therefore `apply` doesn't have a function which to call with an overwritten context
let apply = console.log.apply;
apply(console, ['example']) // error, `this.call is not a function`
like image 34
nem035 Avatar answered Sep 21 '22 12:09

nem035