What is Function.prototype.call() for?

Asked

Viewed 146 times

8

I get confused in using the method call(). It serves to call a function, inherit the parameters of a function or the properties?

Another question of mine is in relation to the keyword this passed as parameter to the method call(), for example:

function A (n1, n2, n3) {
   this.n1 = n1;
   this.n2 = n2;
   this.n3 = n3;

   this.numbers = () => {
       console.log(`${n1}, ${n2}, ${n3}`)
   };
}

function B (n1, n2, n3) {
   A.call(this, n1, n2, n3);

   this.numbers = () => {
       console.log(`${n1}, ${n2}, ${n3}`);
   };
}

let a1 = new A(10, 20, 30);
let b1 = new B(100, 200, 300);

console.log(a1.numbers());
console.log(b1.numbers());

The keyword this that is within the method call() are you referring to whom? To the builder A or B? And why should it be passed there as a parameter? I say this because the MDN mentions that its use is optional in the method call().

In the first example is used the this as a method parameter call(), but in this second example no:

function A (n1, n2, n3) {
   this.n1 = n1;
   this.n2 = n2;
   this.n3 = n3;

   this.numbers = () => {
       console.log(`${n1}, ${n2}, ${n3}`)
   };
}

function B (n1, n2, n3) {
   A.call(n1, n2, n3);

   this.numbers = () => {
       console.log(`${n1}, ${n2}, ${n3}`);
   };
}

let a1 = new A(10, 20, 30);
let b1 = new B(100, 200, 300);

console.log(a1.numbers());
console.log(b1.numbers());

So either use it, since the same results will be returned? What effect will it cause on using and not using?

2 answers

4

You just need to use the call if you want to reset to which object a method is bound for that execution.

The this js is dynamic. So you can transfer methods between objects and everything works.

The call allows you to execute a method in the context of an object, without needing to link this method to the object. This example should clarify:

let A = {
  x: 2,
  dobro() {
    return this.x * 2
  }
};

let B = {
  x: 3
};

let C = {
  x: 4
};

B.dobro = A.dobro;

console.log('Dobro of B.x:', B.dobro());
console.log('Dobro of C.x:', A.dobro.call(C));

console.log('Attributes of B:', Object.keys(B));
console.log('Attributes of C:', Object.keys(C));

As you can see, the double is presented correctly for objects B and C, but C did not incorporate the method. This prevents pollution of objects or allows meta-programming of more elaborate forms.

  • 1

    It is not very clear to my fellow Member on the points mentioned in the question, if further details can be strengthened in the reply, thank you!

3


In Javascript, the this is very dynamic, varying according to the context. And call is one of the ways to control it. For example, if I have a function that returns this:

function bla() {
    return this;
}

console.log(bla());

// se rodar no snippet do site, como é em um browser, o this é igual a window
console.log(bla() == window); // true

If you’re not in the mood Strict (in which this is undefined), the function returns the global object (window, if you are in the browser).

But using call, we can change the this that the function sees:

function bla() {
    return this;
}

let sereiUmNovoThis = { id: 1, nome: 'Novo this' };
console.log(bla.call(sereiUmNovoThis)); // { id: 1, nome: 'Novo this' }

//---------------------------------------------
// exemplo com parâmetro
function imprimeProp(propName) {
    console.log(this[propName]);
}

imprimeProp.call({ id: 1 }, 'id'); // 1
imprimeProp.call([1, 2, 3], 'length'); // 3

Note that if the function has parameters, these are also passed to call. Generally speaking, in doing:

funcao.call(p0, p1, p2, p3, ..pn);

p0 becomes the this, and the others (from p1 until pn) are passed as arguments to the function.

That is to say, imprimeProp.call({ id: 1 }, 'id'); causes the object to { id: 1 } be the this, and the string 'id' is the first argument of the function (ie, propName in this case it will have the value 'id').

In the second case, the array [1, 2, 3] is the this and the string 'length' is the value of propName.


The documentation quotes several examples of use, then I won’t keep repeating one by one.

As for your example, let’s modify it a little to better understand what happened:

function A (n1, n2, n3) {
    console.log(`recebido: ${this}, ${n1}, ${n2}, ${n3}`);
    this.v1 = n1;
    this.v2 = n2;
    this.v3 = n3;

    this.numbers = () => {
        console.log(`${n1}, ${n2}, ${n3} - ${this.v1}, ${this.v2}, ${this.v3}`);
    };
}

function B (n1, n2, n3) {
    A.call(n1, n2, n3);

    this.numbers = () => {
        console.log(`${n1}, ${n2}, ${n3} - ${this.v1}, ${this.v2}, ${this.v3}`);
    };
}

console.log('criando a1');
let a1 = new A(10, 20, 30);
console.log(a1);
a1.numbers();

console.log('criando b1');
let b1 = new B(100, 200, 300);
console.log(b1);
b1.numbers();

The exit is:

criando a1
recebido: [object Object], 10, 20, 30
{
  "v1": 10,
  "v2": 20,
  "v3": 30,
  "numbers": () => {
        console.log(`${n1}, ${n2}, ${n3} - ${this.v1}, ${this.v2}, ${this.v3}`);
    }
}
10, 20, 30 - 10, 20, 30
criando b1
recebido: 100, 200, 300, undefined
{
  "numbers": () => {
        console.log(`${n1}, ${n2}, ${n3} - ${this.v1}, ${this.v2}, ${this.v3}`);
    }
}
100, 200, 300 - undefined, undefined, undefined

Let’s go in pieces, first let’s see what happens to a1:

  • new A(10, 20, 30) calls the function A passing the values 10 to n1, 20 to n2 and 30 to n3
  • these values are assigned respectively to this.v1, this.v2 and this.v3
  • the method is also created numbers, which prints the values of n1, n2 and n3, besides this.v1, this.v2 and this.v3

In this case, the values are correct as expected. I printed the a1 and we can see the values of this.v1, this.v2 and this.v3, equal to those that were passed. The method numbers also printed the values correctly.

Now, what happens to b1?

  • new B(100, 200, 300) calls the function B passing the values 100 to n1, 200 to n2 and 300 to n3
  • the call is made to A.call(n1, n2, n3). That is to say, n1 is the first argument, then in that case he’s the this (we can see this in console.log(`recebido: ${this}, ${n1}, ${n2}, ${n3}`), see that this is equal to 100). With that, n1 received the value 200, n2 received 300 and n3 got undefined.
  • the method numbers prints the values of n1, n2 and n3, but not the values that A received, and yes those who B received (as this is defined within B). That’s why he prints 100, 200, 300 correctly.
  • but note that when printing b1, the values of v1, v2 and v3. So when printing this.v1, this.v2 and this.v3, everyone is undefined

And if we pass the this as the first argument of call?

function A (n1, n2, n3) {
    console.log(`recebido: ${this}, ${n1}, ${n2}, ${n3}`);
    this.v1 = n1;
    this.v2 = n2;
    this.v3 = n3;

    this.numbers = () => {
        console.log(`${n1}, ${n2}, ${n3} - ${this.v1}, ${this.v2}, ${this.v3}`);
    };
}

function B (n1, n2, n3) {
    A.call(this, n1, n2, n3); // passei o this como argumento

    this.numbers = () => {
        console.log(`${n1}, ${n2}, ${n3} - ${this.v1}, ${this.v2}, ${this.v3}`);
    };
}

console.log('criando a1');
let a1 = new A(10, 20, 30);
console.log(a1);
a1.numbers();

console.log('criando b1');
let b1 = new B(100, 200, 300);
console.log(b1);
b1.numbers();

Now the way out is:

criando a1
recebido: [object Object], 10, 20, 30
{
  "v1": 10,
  "v2": 20,
  "v3": 30,
  "numbers": () => {
        console.log(`${n1}, ${n2}, ${n3} - ${this.v1}, ${this.v2}, ${this.v3}`);
    }
}
10, 20, 30 - 10, 20, 30
criando b1
recebido: [object Object], 100, 200, 300
{
  "v1": 100,
  "v2": 200,
  "v3": 300,
  "numbers": () => {
        console.log(`${n1}, ${n2}, ${n3} - ${this.v1}, ${this.v2}, ${this.v3}`);
    }
}
100, 200, 300 - 100, 200, 300

Now yes. In doing A.call(this, n1, n2, n3), I’m passing the this, which in this case concerns B (since I’m in the role B). I mean, I’m calling A, but she considers that B is the this. Hence the values of this.v1, this.v2 and this.v3 are set correctly in B.

By the way, this is exactly the first example of documentation. Is a form of B "reuse" the constructor function A. In fact, as we are now passing the this correct, the function B or need to redefine numbers (because this would already be created properly within A):

function A (n1, n2, n3) {
    console.log(`recebido: ${this}, ${n1}, ${n2}, ${n3}`)
    this.v1 = n1;
    this.v2 = n2;
    this.v3 = n3;

    this.numbers = () => {
        console.log(`${n1}, ${n2}, ${n3} - ${this.v1}, ${this.v2}, ${this.v3}`);
    };
}

function B (n1, n2, n3) {
    A.call(this, n1, n2, n3);
}

let b = new B(1, 2, 3);
b.numbers();

How I passed this for A (and this is B), then this.numbers defines numbers in B, and everything works as expected.

Perhaps what is most confusing in this case is the fact of this to mean something different every moment.


As regards the first argument to be optional, the documentation cites that indeed it is, but then you can’t pass any other argument (it would have to be just A.call()). In this case, the this becomes the global object (or undefined if you’re in mode Strict).

function bla() {
    return this;
}

// se rodar no snippet do site, como é em um browser, o this é igual a window
console.log(bla.call() == window); // true

function blaStrict() {
    "use strict";
    return this;
}

console.log(blaStrict.call()); // undefined


It’s not something directly related to the question, but actually, the way you did, a new function numbers is always created each time you create a new A or B. The ideal would be to declare the function in the prototype of A, or use the ES6 class syntax. As it is not the focus of the question, I leave some references: that one, that one, that one and that one.

  • 1

    Very good answer, thank you even helped a lot in clarifying my doubts :).

Browser other questions tagged

You are not signed in. Login or sign up in order to post.