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.
Related: When trying to create an object using
this
the console says the property is Undefined– Icaro Martins