TransWikia.com

Para que serve o método Function.prototype.call()?

Stack Overflow em Português Asked by felipe cardozo on September 26, 2021

Eu fico confuso em utilizar o método call(). Ele serve para chamar uma função, herdar os parâmetros de uma função ou as propriedades?

Outra dúvida minha é em relação a palavra-chave this passada como parâmetro para o método call(), por exemplo:

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());

A palavra-chave this que está dentro do método call() está se referindo a quêm? Ao construtor A ou B? E por que ela deve estar alí passada como parâmetro? Eu falo isso porque o MDN menciona que o uso dela é opcional no método call().

No primeiro exemplo é usado o this como parâmetro do método call(), mas neste segundo exemplo não:

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());

Então tanto faz usar ela, visto que os mesmos resultados serão retornados? Que efeito isso vai causar em usar e não usar?

2 Answers

Em JavaScript, o this é bem dinâmico, variando conforme o contexto. E call é uma das maneiras de se controlar isso. Por exemplo, se eu tiver uma função que retorna 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

Se não estiver em modo strict (no qual this é undefined), a função retorna o objeto global (window, se estiver no browser).

Mas usando call, podemos alterar o this que a função enxerga:

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

Repare que se a função tiver parâmetros, estes também são passados para call. De forma geral, ao fazer:

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

p0 se torna o this, e os demais (de p1 até pn) são passados como argumentos para a função.

Ou seja, imprimeProp.call({ id: 1 }, 'id'); faz com que o objeto { id: 1 } seja o this, e a string 'id' seja o primeiro argumento da função (ou seja, propName neste caso terá o valor 'id').

No segundo caso, o array [1, 2, 3] é o this e a string 'length' é o valor de propName.


A documentação cita vários exemplos de uso, então não vou ficar repetindo um a um.

Quanto ao seu exemplo, vamos modificá-lo um pouco para entender melhor o que aconteceu:

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();

A saída é:

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

Vamos por partes, primeiro vejamos o que acontece com a1:

  • new A(10, 20, 30) chama a função A passando os valores 10 para n1, 20 para n2 e 30 para n3
  • esses valores são atribuídos respectivamente a this.v1, this.v2 e this.v3
  • também é criado o método numbers, que imprime os valores de n1, n2 e n3, além de this.v1, this.v2 e this.v3

No caso, os valores estão corretos, conforme o esperado. Eu imprimi o próprio a1 e podemos ver os valores de this.v1, this.v2 e this.v3, iguais aos que foram passados. O método numbers também imprimiu os valores corretamente.

Agora, o que acontece com b1?

  • new B(100, 200, 300) chama a função B passando os valores 100 para n1, 200 para n2 e 300 para n3
  • é feita a chamada para A.call(n1, n2, n3). Ou seja, n1 é o primeiro argumento, então nesse caso ele é o this (podemos ver isso em console.log(`recebido: ${this}, ${n1}, ${n2}, ${n3}`), veja que this é igual a 100). Com isso, n1 recebeu o valor 200, n2 recebeu 300 e n3 ficou undefined.
  • o método numbers imprime os valores de n1, n2 e n3, mas não os valores que A recebeu, e sim os que B recebeu (pois este é definido dentro de B). Por isso que ele imprime 100, 200, 300 corretamente.
  • mas repare que ao imprimir b1, não aparecem os valores de v1, v2 e v3. Por isso ao imprimir this.v1, this.v2 e this.v3, todos estão undefined

E se passarmos o this como o primeiro argumento de 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();

Agora a saída é:

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

Agora sim. Ao fazer A.call(this, n1, n2, n3), estou passando o this, que no caso refere-se a B (já que estou dentro da função B). Ou seja, estou chamando A, mas ela considera que B é o this. Por isso os valores de this.v1, this.v2 e this.v3 são setados corretamente em B.

Aliás, isso é exatamente o primeiro exemplo da documentação. É uma forma de B "reaproveitar" a função construtora A. Aliás, como agora estamos passando o this correto, a função B nem precisaria redefinir numbers (pois este já seria criado corretamente dentro de 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();

Como eu passei this para A (e este é B), então this.numbers define numbers em B, e tudo funciona como o esperado.

Talvez o que mais confunda neste caso é o fato do this significar uma coisa diferente a cada momento.


Quanto ao primeiro argumento ser opcional, a documentação cita que de fato é, mas aí você não pode passar nenhum outro argumento (teria que ser apenas A.call()). Neste caso, o this passa a ser o objeto global (ou undefined se estiver no modo 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


Não é algo diretamente relacionado à pergunta, mas na verdade, da forma que você fez, uma nova função numbers sempre é criada cada vez que você cria um novo A ou B. O ideal seria declarar a função no prototype de A, ou usar a sintaxe de classes do ES6. Como não é o foco da pergunta, deixo algumas referências: essa, essa, essa e essa.

Correct answer by hkotsubo on September 26, 2021

Você só precisa usar o call se quiser redefinir a qual objeto um método está vinculado para aquela execução.

O this em js é dinâmico. Sendo assim você pode transferir métodos entre objetos e tudo funciona.

O call te permite executar um método no contexto de um objeto, sem precisar vincular esse método ao objeto. Esse exemplo deve esclarecer:

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

Como você pode ver, o dobro é apresentado corretamente para os objetos B e C, mas o C não incorporou o método. Isso previne poluição de objetos ou permite meta-programação de formas mais elaboradas.

Answered by Aurium on September 26, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP