Nos últimos 3 posts, você viu as três principais formas de padrão de invocação em JavaScrips e como esses padrões manipulam o valor do “this” nas funções chamadas. Esses padrões de invocação não são as únicas formas que você pode manipular o “this”. Hoje você verá dois métodos que permitem manipular explícitamente o “this” em quase toda chamada de função no JavaScript.
Funções com métodos
Quase tudo no JavaScript são objetos, ou ao menos, se comportam como objetos quando métodos e atributos são acessados. Função em JavaScript não é exceção. Elas podem ser tratadas como a maioria dos objetos, na maior parte do tempo. Elas possuem métodos e atributos que podem ser cancelados ou lidos (mas alguns deles são protegidos e não podem ser modificados).
Entre os métodos que as funções possuem, existem os métodos “.call” e o “.apply”. Estes métodos existem em todas as funções e eles permitem que você invoque uma função com um contexto específico, provendo parâmetros para a função em uma de duas formas distintas.
Utilizando o .call
Ao utilizar o “.call” em uma função, o primeiro parâmetro será o contexto ou a variável “this”. Qualquer parâmetro adicional será enviado como argumentos à função original.
function adicionar(b){
return this.a + b;
}
var meuContexto = {a: 1};
var resultado = adicionar.call(meuContexto, 2);
console.log(resultado); // => 3
Você pode informar qualquer quantidade de argumentos ao método “.call”, e todos eles serão passados à função original após a definição do contexto.
O método “.call” é mais utilizado quando você precisa chamar uma função dinamicamente com um determinado contexto e definir um conjunto de valores que você já possui como variáveis.
Utilizando o .apply
A utilização do “.apply” é essencialmente a mesma do “.call” com exceção que você passa um array como segundo parâmetro e esse array é passado para a função original como uma lista de argumentos.
function adicionar(b, c){
return this.a + b + c;
}
var meuContexto = {a: 1};
var resultado = adicionar.apply(meuContexto, [2, 3]);
console.log(resultado); // => 6
Perceba que qualquer parâmetro que você deseja passar ao “.apply” (após o primeiro, que define o “this”) tem que estar em um array. Se você esquecer de informar parâmetros adicionais como um array, você terá um erro.
O método “.apply” é mais utilizado quando você precisa chamar uma função dinamicamente com um determinado contexto e um conjunto de valores em um array, tipo separando os argumentos de outra função.
Similares, mas diferentes
Utilizando “.call” ou “.apply” é algo muito parecido, mas ligeiramente diferente na forma da passagem de parâmetros à função original. Com o “.call” os parâmetros são passados como uma lista de parâmetros comuns. com o “.apply” os parâmetros são passados como um array. A ligeira diferença entre as duas formas de invocação de métodos permite muita flexibilidade no seu código. Com “.apply”, você pode dinamicamente acessar métodos e passar parâmetros sem precisar saber quantos parâmetros são necessários. Com o “.call”, você define o contexto do método e passa uma lista específica de parâmetros que você já conhece.
Padrões de sobrecarga de invocação
Um dos efeitos colaterais do uso do “.apply” e do “.call”, se você não tiver cuidado, é que eles podem sobrescrever o contexto que pode ter sido definido pela invocação do método ou função.
Se, por exemplo, você tiver um objeto com atributo e método:
var meuObjeto = {
nome: "meu nome",
fazerAlgo: function(){
console.log(this.nome);
}
};
e você chamar o método com “.call” ou “.apply”, você está realizando uma sobrecarga no padrão de invocação objeto-ponto-método.
meuObjeto.fazerAlgo.call({});
Neste exemplo, “fazerAlgo.call” não segue o padrão objeto-ponto-método que foi explicado no segundo post da série. Em vez disso, “.call” sobrecarrega o valor do “this” para a execução do “fazerAlgo”. E com o contexto “this” vazio passado por parâmetro na chamada acima, não existirá nenhum atributo “nome” e o resultado do “console.log” será “undefined”.
Onde usar
A utilização do “.call” e “.apply” se torna importante quando utilizamos objetos e métodos callback. Por exemplo:
var meuObjeto = {
nome: "Meu Nome",
algumaCoisa: function(cb){
cb();
},
fazerAlgo: function(){
console.log(this.nome);
}
};
meuObjeto.algumaCoisa(meuObjeto.fazerAlgo);
Neste exemplo (que por sinal é muito simples somente para ilustrar o caso) a chamada para “meuObjeto.algumaCoisa” recebe “meuObjeto.fazerAlgo” como parâmetro. Desde que o parâmetro não inclua o “()” (que invocaria a função antes de executar o “algumaCoisa”) para que a função seja passada como um ponteiro para função, se tornando efetivamente um método callback. O método callback é então invocado no padrão de chamadas diretas, ou seja, o valor do “this” recai no contexto que já vimos, ou será o contexto global ou indefinido, dependendo do ambiente de execução (como foi mencionado no post 1).
Uma vez que a chamada de “cb()” aponta para o método “fazerAlgo”, o valor do “this” não será “meuObjeto” e por decorrência não contém o atributo “nome”. Então, o resultado exibido pelo “console.log” será algum valor inexistente ou mesmo undefined.
As ramificações disso são significativas. Esta combinação de diversos padrões podem levá-lo a diversos caminhos errados ao analisar qual o real valor do “this”. Entretando, é o padrão de chamada direta do “cb()” que determina o valor do “this” e não qualquer outro padrão de chamada abordados aqui.
Muita coisa pra digerir
Eu sei que é muita coisa pra digerir. Você viu anteriormente os principais padrões de invocação de funções. Agora você viu dois métodos adicionais para modificação e sobrecarga do valor do “this” e como podem ser utilizados em conjunto com os padrões aneriores. Realmente isso pode ser maçante no início. Mas não se preocupe se você ficar meio perdido, é normal. Dê algum tempo para compreender. O melhor a fazer é fazer experimentos no código e ver o que acontece.
Cenas do próximo capítulo
Você sabia que existe uma última dica neste exemplo? No que diz respeito ao padrão de invocação de chamada direta do “cb()” é possível garantir o valor do “this” dentro da função “fazerAlgo”, utilizando o “.bind”. Mas isso fica para o próximo post desta série Dominando o “this” do JavaScript.
Se houver alguma dúvida, sugestão ou percebeu algum erro de digitação, não hesite em entrar em contato que responderei tão rápido quanto puder e atribuirei o crédito pela ajuda.
Até mais!
André Fellows