W3docs

Decoradores e Encaminhamento em JavaScript: call e apply

Aprenda a escrever funções decoradoras em JavaScript e encaminhar chamadas com func.call e func.apply, incluindo decorador de cache, bind e empréstimo de métodos.

Um decorador é uma função invólucro: recebe outra função e retorna uma nova função que adiciona comportamento — registro, cache, temporização, verificações de acesso — ao redor da original, sem modificar o código dela. Para construir decoradores que funcionem com qualquer função, é necessário uma forma confiável de chamar uma função com um this escolhido e um conjunto de argumentos escolhido. É exatamente isso que func.call e func.apply fornecem.

Este capítulo abrange funções decoradoras (invólucros), encaminhamento de this e argumentos com call/apply, restauração de contexto perdido com bind, e empréstimo de métodos.

Nota: Isto trata de decoradores de função — o padrão cotidiano disponível em JavaScript puro hoje em dia. Os mais recentes decoradores de classe com prefixo @ são um recurso separado e mais avançado (atualmente uma proposta Stage 3 que requer um transpilador), e não são abordados aqui.

O que é um decorador

Um decorador é uma função que envolve uma função-alvo e retorna um substituto com comportamento extra. Como o invólucro tem a mesma forma externa, os chamadores não precisam mudar.

function sum(a, b) {
  return a + b;
}

function logged(func) {
  return function (a, b) {
    console.log(`calling with ${a}, ${b}`);
    return func(a, b);
  };
}

const loggedSum = logged(sum);
console.log(loggedSum(2, 3));
// calling with 2, 3
// 5

O invólucro é reutilizável, mantém o original intacto e pode ser empilhado. O problema acima é que ele só lida com uma função que recebe exatamente dois argumentos e nenhum this. Para envolver qualquer função, encaminhamos a chamada.

Um decorador de cache

Um decorador comum no mundo real armazena em cache os resultados para que uma função custosa execute apenas uma vez por entrada. Experimente:

javascript— editable

Isso funciona para uma função independente. Mas no momento em que slow é um método que usa this, chamar func(x) o quebra — o invólucro perde o contexto do objeto. É aí que call e apply entram.

Encaminhando a chamada: call e apply

call e apply ambos invocam uma função com um this explicitamente escolhido. Eles diferem apenas na forma como os argumentos são passados:

  • func.call(thisArg, arg1, arg2, ...) — argumentos listados individualmente.
  • func.apply(thisArg, argsArray) — argumentos como um único array (ou similar a array).

call

javascript— editable

apply

javascript— editable

Estas duas chamadas são equivalentes:

func.call(obj, 1, 2, 3);
func.apply(obj, [1, 2, 3]);

Use call quando você conhece os argumentos individualmente; use apply quando já os tem em um array. Com a sintaxe spread (func.call(obj, ...args)) a distinção frequentemente desaparece — veja Parâmetros rest e sintaxe spread.

Encaminhando this com call

Agora podemos corrigir o decorador de cache para métodos. Dentro do invólucro, this é o objeto no qual o método foi chamado, então o encaminhamos com func.call(this, x):

javascript— editable

Sem func.call(this, x), a chamada interna seria func(x) e this seria perdido, fazendo this.someMethod() falhar.

Encaminhando todos os argumentos com apply

Para um método com vários argumentos, encaminhe todos os argumentos de uma vez. O invólucro não sabe quantos há, então os lê de arguments e os passa todos através de func.apply(this, arguments):

javascript— editable

Passar this e arguments diretamente é chamado de encaminhamento de chamada: o invólucro se comporta exatamente como o original, apenas com lógica extra ao redor.

Empréstimo de métodos

A função hash acima usa um truque. arguments é similar a array (tem índices e length) mas não é um array real, portanto não tem join. Em vez de converter, nós emprestamos o método de array:

function hash(args) {
  return [].join.call(args, ',');
}
console.log(hash([3, 5])); // "3,5"

[].join é Array.prototype.join. Chamá-lo com args como this executa a lógica de join sobre o valor similar a array. O empréstimo de métodos permite reutilizar métodos nativos em objetos que não são daquele tipo.

bind e contexto perdido

call e apply invocam imediatamente. bind em vez disso retorna uma nova função com this permanentemente fixado — útil quando a chamada acontece depois (um callback, um manipulador de eventos, um setTimeout).

O problema que bind resolve é a perda de contexto: desanexe um método de seu objeto e this não aponta mais para ele.

javascript— editable

Para uma análise mais aprofundada sobre como corrigir contexto em callbacks e a diferença entre bind, arrow functions e call/apply, veja Vinculação de funções.

Quando usar qual

ObjetivoUse
Chamar agora com this escolhido, args listados individualmentefunc.call(thisArg, a, b)
Chamar agora com this escolhido, args já em um arrayfunc.apply(thisArg, args)
Obter uma função para chamar depois com this fixadofunc.bind(thisArg)
Reutilizar um método nativo em um objeto similar a arrayemprestá-lo: [].method.call(obj, …)

Conclusão

Decoradores envolvem uma função para adicionar comportamento sem alterá-la. Para fazer um invólucro funcionar com qualquer função — incluindo métodos — encaminhe a chamada original com func.call(this, ...) ou func.apply(this, arguments), use bind quando a chamada for adiada, e empreste métodos nativos quando um objeto for apenas similar a array. Juntos, esses recursos fornecem abstrações reutilizáveis e seguras quanto ao contexto, como o decorador de cache acima.

Leitura relacionada: Métodos de object, "this", Objeto de função, NFE, e Vinculação de funções.

Prática

Prática
Quais afirmações descrevem com precisão o uso e as diferenças entre os métodos `call` e `apply` em JavaScript?
Quais afirmações descrevem com precisão o uso e as diferenças entre os métodos `call` e `apply` em JavaScript?
Was this page helpful?