A palavra-chave this
em JavaScript confunde desenvolvedores JavaScript novos e experientes. Este artigo pretende elucidar a questão de uma vez, deixando claro como usar this
corretamente em todos os cenários, incluindo situações mais peculiares em que normalmente this
se revela mais difícil de ser compreendido.
Em JavaScript, usa-se this
de forma semelhante ao uso de pronomes em linguagens naturais, como o inglês ou francês. Escreve-se: “João está correndo rápido porque ele está tentando pegar o trem”. Note o uso do pronome “ele”. Poderia se ter escrito: “João está correndo rápido porque João está tentando pegar o trem”. Não se reutiliza “João” dessa maneira, pois se assim fosse, nossa família, amigos e colegas nos abandonariam… De uma maneira graciosamente semelhante, em JavaScript se usa a palavra-chave this
como um atalho, um referente; ou seja, o sujeito no contexto ou o sujeito do código em execução.
Exemplo:
1 2 3 4 5 6 7 8 9 10 11 |
var person = { firstName : "Penelope", lastName : "Barrymore", fullName : function() { // Notou o uso do "this" tal como se usou "ele" no exemplo da frase anterior? console.log( this.firstName + ' ' + this.lastName ); // Também poderia se ter escrito: console.log( person.firstName + ' ' + person.lastName ); } } |
Se se usa person.firstName
e person.lastName
, tal como no último exemplo, o código se torna ambíguo. Considere que poderia haver outra variável global (você estando ciente dela ou não) com o nome “person”. Em seguida, as referências a person.firstName
poderiam tentar acessar a propriedade firstName
da variável global person
e isso poderia levar a erros difíceis de serem depurados. Portanto, usa-se a palavra-chave this
não apenas para fins “estéticos” (isto é, como um referente), mas, também, para fins de precisão. Seu uso realmente torna o código mais inequívoco, assim como o pronome “ele” tornou a frase mais clara, informando que se estava referindo ao João específico do início da frase.
Assim como o pronome “ele” é usado para se referir ao antecedente (substantivo a que um pronome se refere), a palavra-chave this
é similarmente usada para se referir a um objeto a que a função (onde this
é usado) está vinculada. Esta palavra-chave não se refere apenas ao objeto, mas também contém o valor do objeto. Assim como o pronome, isso pode ser pensado como um atalho (ou um substituto razoavelmente não-ambíguo) para se referir ao objeto no contexto (o “objeto antecedente”).
O básico sobre this
em JavaScript
Primeiramente, é preciso saber que todas as funções em JavaScript têm propriedades, assim como os objetos têm propriedades. E quando uma função é executada, ela obtém a propriedade this
— uma variável com o valor do objeto que invoca a função na qual this
é usado.
A referência ao this
sempre se refere a (e contém o valor de) um objeto — um objeto singular — e normalmente é usado dentro de uma função ou método, embora possa ser usado fora de uma função no escopo global.
this
é usado dentro de uma função (digamos função “A”) e ele contém o valor do objeto que invoca a função A. Isso é preciso para acessar métodos e propriedades do objeto que invoca a função A, especialmente porque nem sempre é possível saber o nome do objeto invocador e, às vezes, não há nenhum nome para usar para se referir ao objeto invocando. Na verdade, this
é realmente apenas um atalho-referência ao “objeto antecedente” (ou o objeto invocador).
Reflita sobre este exemplo básico que ilustra o uso de this
em Javascript:
1 2 3 4 5 6 7 8 9 10 11 12 |
var person = { firstName : "Penelope", lastName : "Barrymore", // Já que a palavra-chave "this" é usada dentro do método showFullName() abaixo e este método // é definido no objeto "person", "this" terá o valor do objeto "person" porque este // invocará showFullName(). showFullName : function() { console.log ( this.firstName + ' ' + this.lastName ); } } person.showFullName(); // Penelope Barrymore |
E também considere este exemplo em jQuery:
1 2 3 4 |
$( 'button' ).on( 'click', function( event ) { // $( this ) terá o valor do objeto "button" porque ele invoca o clique console.log ( $( this ).prop( 'name' ) ); } ); |
Explicando melhor o exemplo jQuery anterior: o uso de $( this )
, que é a sintaxe da jQuery para esta palavra-chave em JavaScript, é usado dentro de uma função anônima, que é executada no “on click” do botão. A razão pela qual $( this )
está vinculado ao objeto de botão é porque a jQuery vincula $( this )
ao objeto que invoca o método de clique. Portanto, $( this )
terá o valor do objeto jQuery ($( 'button' )
) mesmo que $( this )
seja definido dentro de uma função anônima que não pode acessar a variável “this” na função externa.
Importante ressaltar que o botão é um elemento DOM na página HTML e também é um objeto; neste caso, é um objeto jQuery porque está envolvido na função jQuery $()
.
A maior pegadinha com this
em JavaScript
Se você entender este princípio de JavaScript, você entenderá a palavra-chave “this”: não é atribuído um valor a this
até que um objeto invoque a função na qual this
é definido.
Para fins didáticos, considere onde this
está definido como “Função this”. Mesmo que pareça que this
se refere ao objeto onde ele é definido, isso só acontece quando um objeto chama a “Função this” a que this
está atualmente atribuído. E o valor atribuído é baseado exclusivamente no objeto que invoca a “Função this”.
this
tem o valor do objeto sendo invocado na maioria das circunstâncias. No entanto, existem alguns cenários nos quais this
não tem o valor do objeto sendo invocado. Cenários estes visto mais à frente.
O uso de this
em escopo global
No escopo global, quando o código está sendo executado no navegador, todas as variáveis globais e funções são definidas no objeto window
. Portanto, quando se usa this
em uma função global, ele se refere a (e tem o valor de) o objeto window
global — não no strict mode, como observado anteriormente –, que é o contêiner principal de toda a aplicação JavaScript ou página da web.
Deste modo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
var firstName = 'Peter', lastName = 'Ally'; function showFullName () { // "this" dentro dessa função terá o valor do objeto "window" porque a função "showFullName" // é definida no escopo global, assim como "firstName" and "lastName". console.log( this.firstName + ' ' + this.lastName ); } var person = { firstName : "Penelope", lastName : "Barrymore", showFullName : function() { // "this" se refere ao objeto "person", já que a função "showFullName" será invocada // pelo objeto "person". console.log( this.firstName + ' ' + this.lastName ); } } showFullName(); // Peter Ally // "window" é o objeto em que todas as variáveis globais e funções são definidas, portanto: window.showFullName(); // Peter Ally // "this" dentro do método "showFullName", que é definido dentro do objeto "person", ainda se refere // ao objeto "person", então: person.showFullName(); // Penelope Barrymore |
Quando this
é mal compreendido e se torna complicado
A palavra-chave this
é mais mal compreendida em “métodos emprestados”; quando se atribui um método que usa this
para uma variável; quando uma função que usa this
é passada como uma função de callback e; quando this
é usado dentro de uma closure (uma função interna).
Ajustando this
quando usado em um método callback
As coisas ficam um pouco cabeludas quando passamos um método (que usa this
) como um parâmetro para ser usado como uma função de retorno (callback). Por exemplo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// Tem-se um objeto simples com o método "clickHandler" para ser usado quando um botão na página // é clicado. var user = { data : [ { name : "T. Woods", age : 37 }, { name : "P. Mickelson", age : 43 } ], clickHandler : function( event ) { // Número aleatório entre 0 e 1 var randomNum = ( ( Math.random() * 2 | 0 ) + 1 ) - 1; // Mostra um nome e idade aleatórios do array "data" console.log ( this.data[ randomNum ].name + ' ' + this.data[ randomNum ].age ); } } // O botão é envolvido por um wrapper de jQuery ($), então ele agora é um objeto jQuery // e a saída será "undefined" porque não há propriedade em "data" no objeto-botão. $( 'button' ).on( 'click', user.clickHandler ); // "Cannot read property '0' of undefined" |
No código acima, uma vez que o botão ($( 'button' )
) é um objeto e se está passando o método user.clickHandler()
no clique como um callback, sabe-se que this
dentro do método user.clickHandler()
não mais se refere ao objeto user
. this
agora se refere ao objeto em que o método user.clickHandler
é executado porque este é definido dentro do método user.clickHandler
. E o objeto que invoca user.clickHandler
é o objeto de botão — user.clickHandler
será executado dentro do método de clique do objeto-botão.
Importante observar que, mesmo que se esteja chamando o método clickHandler()
através de user.clickHandler
(o que é preciso fazer, já que “clickHandler” é um método definido no “user”), o método clickHandler()
será executado com o objeto button
como contexto para que “this” agora se refere. Então, this
agora se refere ao objeto de botão ($( 'button' )
).
Neste ponto, deve ser claro quando o contexto muda — quando se executa um método em algum outro objeto do qual o objeto foi originalmente definido, a palavra-chave this
já não se refere ao objeto original, no qual this
foi originalmente definido, mas se refere ao objeto que invoca o método onde this
foi definido.
Solução para corrigir this
quando um método é passado como uma função de callback
Como realmente se quer que this.data
faça referência à propriedade de dados no objeto “user”, é possível usar os métodos bind()
, apply()
ou call()
para definir especificamente o valor de this
.
Ao invés dessa linha:
1 |
$( 'button' ).on( 'click', user.clickHandler ); |
Deve-se usar bind()
para clickHandler
, assim:
1 |
$( 'button' ).on( 'click', user.clickHandler.bind( user ) ); // P. Mickelson 43 |
Ajustando this
dentro de um closure
Outro exemplo de quando this
é mal compreendido é quando se usa um método interno ou closure. É importante observar que closures não podem acessar a função externa dessa variável usando a palavra-chave this
porque essa variável é acessível somente pela própria função e não por funções internas. Por exemplo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
var user = { tournament : "The Masters", data : [ { name : "T. Woods", age : 37 }, { name : "P. Mickelson", age : 43 } ], clickHandler : function() { // O uso de this.data aqui está certo, já que "this" se refere ao objeto "user" e "data" // é uma propriedade desse objeto. this.data.forEach ( function( person ) { // Mas aqui, dentro da função anônima, "this" não mais se refere ao objeto "user"; // essa função interna não pode acessar o "this" da função externa. console.log( 'A o quê "this" se refere? ' + this ); // [object Window] console.log( person.name + ' está jogando no ' + this.tournament ); // "T. Woods está jogando no undefined" // "P. Mickelson está jogando no undefined" } ); } } // A o quê "this" se refere? [object Window] user.clickHandler(); |
this
dentro da função anônima não pode acessar o this
da função externa, então ele se refere ao objeto global window
(quando o strict mode não está sendo usado).
Solução para manter this
dentro de funções anônimas
Para sanar a questão, usa-se uma prática comum em JavaScript: atribuir o valor de this
a uma variável antes de entrar no forEach
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
var user = { tournament : "The Masters", data : [ { name : "T. Woods", age : 37 }, { name : "P. Mickelson", age : 43 } ], clickHandler : function( event ) { var theUserObj = this; this.data.forEach ( function( person ) { console.log( person.name + ' está jogando no ' + theUserObj.tournament ); } ); } } user.clickHandler(); // "T. Woods está jogando no The Masters" // "P. Mickelson está jogando no The Masters" |
1 |
var that = this; |
Ajustando this
quando método é atribuído a uma variável
Se se atribui um método que usa this
a uma variável, o valor de this
quase escapa à imaginação. Como em:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
// "data" é variável global var data = [ { name : "Samantha", age : 12 }, { name : "Alexis", age : 14 } ]; var user = { // Esse "data" é propriedade do objeto "user" data : [ { name : "T. Woods", age : 37 }, { name : "P. Mickelson", age : 43 } ], showData : function( event ) { // Número aleatório entre 0 e 1 var randomNum = ( ( Math.random() * 2 | 0 ) + 1 ) - 1; // Adiciona uma pessoa aleatória do array "data" ao texto console.log( this.data[ randomNum ].name + ' ' + this.data[ randomNum ].age ); } } // ! var showUserData = user.showData; // Quando a função "showUserData" é executada, os valores mostrados são da global "data", // não do array em "user". showUserData(); // "Samantha 12" |
Solução para manter this
quando um método é atribuído a uma variável
É possível sanar a questão definindo especificamente o valor de this
com o método bind
:
1 2 3 |
var showUserData = user.showData.bind( user ); showUserData(); // "P. Mickelson 43" |
Ajustando this
em “métodos emprestados”
“Métodos emprestados” é uma prática comum no desenvolvimento em JavaScript. Veja o exame da relevância de this
no contexto de métodos emprestados.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
// We have two objects. One of them has a method called avg () that the other doesn't have // So we will borrow the (avg()) method var gameController = { scores : [ 20, 34, 55, 46, 77 ], avgScore : null, players : [ { name : "Tommy", playerID : 987, age : 23 }, { name : "Pau", playerID : 87, age : 33 } ] }; var appController = { scores : [ 900, 845, 809, 950 ], avgScore : null, avg : function() { var sumOfScores = this.scores.reduce ( function ( prev, cur, index, array ) { return prev + cur; } ); this.avgScore = sumOfScores / this.scores.length; } }; // Se o código abaixo for executado, a propriedade "gameController.avgScore" terá a // média da pontuação do array "scores" em "appController". // Este código é somente para ilustrar a situação; a intenção é que "appController.avgScore" // continue "null". gameController.avgScore = appController.avg(); |
this
do método avg
não se referirá ao objeto gameController
; ele se referirá ao objeto appController
porque está sendo chamado no appController
.
Solução para manter this
quando um método é atribuído a uma variável
Para corrigir o problema com a garantia de que o this
dentro do método appController.avg()
se refere a gameController
, usa-se o método apply()
assim:
1 2 3 4 5 |
// Está se usando o método apply(), então o segundo argumento tem que ser um array appController.avg.apply( gameController, gameController.scores ); // A propriedade "avgScore" foi atribuída no objeto "gameController", mesmo com o método emprestado // <code>avg()</code> do objeto <code>appController</code>. console.log( gameController.avgScore ); // 46.4 // appController.avgScore ainda é "null"; somente "gameController.avgScore" foi atualizado console.log( appController.avgScore ); // null |
O objeto gameController
toma emprestado o método avg()
de appController
. this
dentro do método appController.avg()
será definido para o objeto gameController
porque se passa o objeto gameController
como o primeiro parâmetro de apply()
— o primeiro parâmetro no método apply()
sempre define o valor de this
explicitamente.
Palavras finais sobre o this
em JavaScript
Como visto, this
fica um pouco problemático em situações onde o contexto original muda, especialmente em funções de callback, quando invocado com um objeto diferente ou em métodos emprestados. Lembre-se: this
é atribuído ao valor do objeto que invocou a função. Mas, ao terminar de ler este artigo, certamente você aprender e/ou revisou o suficiente sobre this
em JavaScript e agora tem as ferramentas e técnicas necessárias para trabalhar nos cenários mais inóspitos.
Com o advento do ECMAScript 6, alteraram-se algumas das maneiras apresentadas de se trabalhar com o this. Mas não se preocupe, no futuro haverá artigos no desenvolvimento para a web abordando essa atualização.
Até lá, bons códigos e boa sorte!