Nunca existiu uma grande inteligência sem uma veia de loucura. - Aristóteles
O domínio da keyword this em JavaScript é essencial na construção de objetos, herança e eventos. Apesar de exercer papel tão fundamental, é muito natural ver tanto desenvolvedores com certa experiência quanto iniciantes se confundirem com seu uso.
Aqui vou procurar fazer duas coisas. Primeiro mostrarei como raciocinar em termos da palavra this e, em seguida, exemplificarei seu uso, indicando as "pegadinhas" mais comuns a quem o utiliza.
O raciocínio this
Considere o exemplo abaixo de criação de um objeto:
Obs: Inserir este código diretamente no console(que é o resultado do exemplo a seguir) ou executá-lo no JSFiddle trarão resultados diferentes, verifique.
var nome = "A confusão";
var filme = {
nome:"O Iluminado",
ano:1980,
diretor: "Stanley Kubrick",
iniciar: function(){
console.log(this.nome + " está começando: REDRUM!");
},
finalizar: function(){
console.log(this.nome + "acabou.");
}
};
var iniciar = filme.iniciar;
filme.iniciar(); //Caso 1: O Iluminado está começando: REDRUM!
iniciar(); //Caso 2: A confusão está começando!
Note que o "this" é utilizado para não repetir o nome do sujeito da ação. Se o nome do filme mudasse, não precisaríamos mudá-lo em todos os métodos que fazem uso dele. Isso é semelhante ao uso dos pronomes ele, este, aquele que usamos quando não queremos massivamente repetir o sujeito da oração.
Nos nossos casos acima, qual o sujeito da ação iniciar?
No primeiro, o próprio objeto filme é o sujeito, pois é ele(o filme) que invoca a ação iniciar, ele(o filme) se auto-inicia.
O segundo caso é um pouco mais sutil. Nele atribuímos o valor da propriedade "iniciar" do objeto filme à variável global de mesmo nome. O que acontece na prática é isso:
var nome = "A confusão";
var iniciar = function() {
debugger; // investigue agora quem é o this
console.log(this.nome + " está começando: REDRUM!");
};
iniciar(); //A confusão está começando!
Desse modo, fica claro quem é o sujeito da ação iniciar. Ele é o escopo global, isto é, o objeto window do browser. Tornando ainda mais claro, o que acontece acima é o seguinte:
window.nome = "A confusão";
window.iniciar = function() {
console.log(this.nome + " está começando: REDRUM!");
};
window.iniciar(); //A confusão está começando!
Dito isso, acho que a confusão está começando a acabar. Vamos a mais 3 exemplos com o objetivo de praticarmos a identificação dos sujeitos que invocam a ação.
Métodos em Event Callbacks
Veja o exemplo abaixo, você consegue adivinhar o resultado do click do primeiro botão?
O resultado esperado pela maior parte das pessoas que desconhecem o mecanismo de funcionamento do "this" é que o primeiro alerta indicasse "handlerId", mas, na verdade, o que se vê é "buttonId". Por que?
O raciocínio é exatamente o mesmo do que foi exposto na seção anterior. O que acontece é que o trecho a seguir de código:
button.addEventListener('click', handler.click);
Equivale a:
button.onclick = function(event){alert(this.id);};
Assim, quando button.onclick() é invocado, fica fácil entender quem é o sujeito da ação.
E quanto ao segundo botão, o que handler.click.bind(handler)
faz?
Vamos abordar os métodos Call,Apply e Bind em um post específico, mas adianto que o efeito da aplicação do método bind é especificar exatamente qual será o valor do this no callback. Na prática, o que aconteceu foi:
button2.click = function(event){alert(handler.id);};
Mudança de escopo (this=that)
Uma das situações em que o desenvolvedor deve ficar sempre alerta no uso do this é quando trabalhar com as chamadas "nested ou inner functions". Isso porque, como foi visto no artigo sobre closures, toda função cria um escopo novo. Assim, à medida que você coloca funções dentro de funções, você vai mudando também o contexto do objeto this. Vejamos o exemplo a seguir que é responsável por mapear um array de acordo com o input do usuário:
O exemplo acima contém 3 casos em um só. Antes de analisá-los, porém, é importante conhecer a assinatura do método map em JavaScript:
arr.map(callback[, thisArg])
Como é possível ver, o método map recebe uma variável opcional de contexto chamada thisArg. Quando ela não é passada como parâmetro, ela assume o valor undefined.
A função array.prototype.map possui em seu mecanismo interno o seguinte trecho de código, responsável por de fato fazer o mapeamento dos valores do array:
mappedValue = callback.call(thisArg, kValue, k, O);
Assim, voltando ao nosso exemplo:
- No caso1, thisArg recebe o valor undefined, e através de seu mecanismo interno acaba definindo this como window.
- No caso2, utilizamos o mecanismo de closure e não usamos this propriamente dito, mas sim a variável auxiliar that que fará referência ao this em que verdadeiramente estamos interessados. Aqui usar tanto that quanto self é uma prática comum entre desenvolvedores javascript.
- No caso3, thisArg recebe o valor de this antes da chamada do callback que é justamente o input número em que atrelamos o evento. Vale notar que neste caso qualquer outro objeto poderia ser especificado para o parâmetro thisArg. Veremos o funcionamento do método call no próximo post.
Quiz
Faça um quiz sobre a keyWord this neste post do Stack Overflow. Nele você vai encontrar diversas situações e com base no que foi apresentado aqui será capaz de prever todas as respostas corretamente.
Próximos Passos
Como você deve ter percebido, saber dominar o this é extremamente importante em JavaScript. Aqui porém fizemos uso dos métodos Bind e Call sem muitas explicações.
E é justamente pra cobrir essa necessidade que virá o próximo post:
- Aprenda Para Sempre Call, Apply e Bind em JavaScript
Será explorado também separadamente o mecanismo de funcionamento dos construtores em Javascript. Nele você vai reconhecer que o nosso amigo this novamente exerce fundamental importância:
- Os Segredos dos Construtores em JavaScript