bind(), call() e apply() em JavaScript: perceber e dominar o this

Porque é que o this muda de valor consoante a forma de chamar uma função, e como call(), apply() e bind() o fixam: exemplos executados, casos dos callbacks e das classes, comparação com as arrow functions.

bind(), call() e apply() em JavaScript: mudar o this
Resposta rápida

call() e apply() executam imediatamente uma função com um this à escolha; o call recebe os argumentos um a um, o apply recebe-os num array. O bind() não executa nada: devolve uma nova função cujo this (e eventualmente os primeiros argumentos) fica fixado para sempre. Usa-se quando um método é passado como callback e perde o seu objeto.

Todas as funções de JavaScript têm três métodos, call(), apply() e bind(), ao serviço da mesma causa: decidir quanto vale this no momento em que a função é executada. Para perceber para que servem, é preciso perceber primeiro o problema que resolvem: em JavaScript, this não depende do sítio onde a função é escrita, mas da forma como é chamada.

O problema: o this muda consoante a chamada

Quatro regras chegam para prever this em quase todos os casos:

  1. Chamada de método objet.fonction(): this é objet.
  2. Chamada simples fonction(): this é undefined em modo estrito (módulos, classes) e o objeto global window em modo não estrito.
  3. Chamada com new: this é o objeto que está a ser construído.
  4. Chamada através de call, apply ou de uma função ligada por bind: this é aquilo que tu decidiste.

As arrow functions escapam a estas regras: não têm this próprio e ficam com o do código que as rodeia.

O caso que dói na prática é a passagem da regra 1 para a regra 2:

probleme.js
const person = {
  name: 'Sarah',
  hello() {
    return `Bonjour ${this.name}`;
  },
};

person.hello();            // 'Bonjour Sarah'  — chamada de método, this = person

const detached = person.hello;
detached();                // TypeError em modo estrito — this vale undefined
                           // ('Bonjour undefined' ou 'Bonjour ' em modo não estrito)

A função não mudou. Mudou apenas a maneira de a chamar, e o this com ela. É exatamente o que acontece quando se passa um método a setTimeout, a addEventListener ou a map.

call(): chamar com um this à escolha

fonction.call(thisArg, arg1, arg2, …) executa a função imediatamente, com this igual a thisArg e os argumentos passados um a um.

call.js
function presenter(age, ville) {
  return `${this.name}, ${age} ans, ${ville}`;
}

const person = { name: 'Sarah' };

presenter.call(person, 30, 'Lille');           // 'Sarah, 30 ans, Lille'
presenter.call({ name: 'Léa' }, 25, 'Paris');   // 'Léa, 25 ans, Paris'

const detached = person.hello;                  // o método do exemplo anterior
detached.call(person);                          // 'Bonjour Sarah' — o this foi reposto

Uma utilização clássica: reaproveitar um método de array em algo que não é um array.

javascript
Array.prototype.slice.call('abc');   // ['a', 'b', 'c']
// Hoje escreve-se antes:
Array.from('abc');                   // ['a', 'b', 'c']

apply(): o mesmo, com os argumentos num array

fonction.apply(thisArg, [arg1, arg2, …]) faz exatamente o que faz call, mas recebe os argumentos sob a forma de array. Prático quando já chegam agrupados.

apply.js
presenter.apply(person, [30, 'Lille']);   // 'Sarah, 30 ans, Lille'

const notes = [3, 9, 4];
Math.max.apply(null, notes);   // 9 — Math.max espera argumentos separados, não um array

Desde o ES2015, o operador de spread torna o apply raramente necessário: Math.max(...notes) e presenter.call(person, ...args) fazem o mesmo trabalho sem perder legibilidade. O apply aparece sobretudo em código escrito antes disso.

bind(): fixar o this para mais tarde

Onde call e apply executam de imediato, bind não executa nada. Devolve uma nova função cujo this fica fixado de vez. É a ferramenta dos callbacks: prepara-se a função agora, outra pessoa chama-a mais tarde.

bind.js
const bound = person.hello.bind(person);

bound();                          // 'Bonjour Sarah', seja qual for o contexto da chamada
bound.call({ name: 'X' });        // 'Bonjour Sarah' — o bind ganha, o call já não muda o this
bound.name;                       // 'bound hello'

O caso típico: um método passado como callback

compteur.js
class Compteur {
  constructor() {
    this.n = 0;
  }
  incrementer() {
    this.n += 1;
    return this.n;
  }
}

const compteur = new Compteur();

// Sem bind — o método está separado do seu objeto
const inc = compteur.incrementer;
inc();   // TypeError: Cannot read properties of undefined (reading 'n')

// Com bind — o this fica fixado em compteur
const incLie = compteur.incrementer.bind(compteur);
incLie();   // 1
incLie();   // 2

O mesmo esquema aplica-se aos temporizadores e aos eventos do navegador:

javascript
setTimeout(compteur.incrementer.bind(compteur), 1000);
bouton.addEventListener('click', compteur.incrementer.bind(compteur));

// Para poderes remover o listener depois, guarda a referência ligada:
const onClick = compteur.incrementer.bind(compteur);
bouton.addEventListener('click', onClick);
bouton.removeEventListener('click', onClick);

Sem bind, o addEventListener chamaria incrementer com this igual ao botão clicado: this.n seria undefined e o contador não contaria nada.

bind() para a aplicação parcial

Os argumentos passados a bind a seguir ao this também ficam fixados. A função devolvida espera os seguintes.

partiel.js
const presenterSarah = presenter.bind(person, 30);
presenterSarah('Lille');   // 'Sarah, 30 ans, Lille' — só falta fornecer o último argumento

const add = (a, b) => a + b;
const add5 = add.bind(null, 5);   // o this não serve aqui — basta null
add5(3);                          // 8

É uma forma simples de especializar uma função genérica sem escrever uma função invólucro.

Arrow functions: quando o bind deixa de servir

Uma arrow function capta o this do sítio onde é escrita e ignora tudo o resto. Daí duas consequências.

Primeiro, call, apply e bind não conseguem mudar o seu this:

javascript
const arrow = () => this;
arrow.call({ a: 1 });   // o this do módulo, não { a: 1 }

Depois, e é este o uso corrente, uma arrow declarada dentro de um método ou de uma classe mantém o this certo sem bind:

fleche.js
class Compteur {
  n = 0;

  // Propriedade arrow — o this é capturado de uma vez por todas
  incrementer = () => {
    this.n += 1;
    return this.n;
  };
}

const c = new Compteur();
const inc = c.incrementer;
inc();   // 1 — funciona sem bind

setTimeout(() => c.incrementer(), 1000);   // ou uma arrow na chamada

O reverso da medalha: uma propriedade arrow é recriada em cada instância, ao passo que um método clássico é partilhado pelo protótipo. Para alguns objetos, não tem importância; para milhares de instâncias, o método ligado uma vez no construtor (this.incrementer = this.incrementer.bind(this)) poupa memória.

O this passado como segundo argumento

Vários métodos de array aceitam diretamente o valor de this a usar no callback, o que evita um bind:

javascript
const config = { k: 10 };
[1, 2, 3].map(function (x) { return this.k * x; }, config);   // [10, 20, 30]

forEach, filter, some e every têm o mesmo segundo argumento. Com uma arrow function, deixa de ser preciso.

Tabela comparativa

call apply bind
Executa a função imediatamente imediatamente não: devolve uma função
Argumentos um a um num array um a um, fixados para as chamadas futuras
Utilização típica reaproveitar um método espalhar um array (substituído por ...) callbacks, eventos, aplicação parcial
Efeito numa arrow function nenhum sobre this nenhum sobre this nenhum sobre this, os argumentos ficam fixados

Erros frequentes e como os ler

  • «Cannot read properties of undefined (reading ‘n’)» num método de classe: o método foi separado da sua instância (regra 2). Liga-o com bind ou declara-o como propriedade arrow.
  • Um listener de evento que não altera o estado: this é o elemento do DOM, não o teu objeto. O remédio é o mesmo.
  • Um bind que parece não fazer nada: a função é uma arrow. Tira o bind, não serve para nada.
  • Um removeEventListener que não retira nada: cada bind cria uma função nova; é preciso passar a mesma referência ligada na adição e na remoção.

Para a seguir, as classes em JavaScript voltam aos métodos e ao protótipo, e o capítulo As funções do tutorial assenta as bases se o vocabulário deste artigo ainda for novo. Em PHP, a questão põe-se de outra maneira: $this nunca muda de objeto.

Erros frequentes

Passar um método como callback sem bind bouton.addEventListener('click', compteur.incrementer) chama a função com this igual ao botão, não a compteur. Escreve compteur.incrementer.bind(compteur) ou uma arrow function.
Julgar que o bind numa arrow function muda o this Uma arrow function não tem this próprio: call, apply e bind não o alteram. Só os argumentos passados pelo bind contam.
Chamar o bind a cada renderização O bind cria uma função nova em cada chamada. Num ciclo ou numa renderização repetida, liga uma vez no construtor (ou declara o método como propriedade arrow).
Confundir call e apply com a mesma lista de argumentos f.call(obj, [1, 2]) passa um único argumento, o array. Para espalhar um array é o apply, ou f.call(obj, ...tableau).

JavaScript

Damien Flandrin Programador web desde 2010, criador da Gekkode e do Email Impact. Cada artigo é testado num projeto real antes de ser publicado. Contacto
Newsletter

Os novos testes, tutoriais e projetos, por e-mail.

Testes reproduzíveis, código versionado, resultados datados. Nunca spam.