What is this ?

Olá povo bonito!

Não estou inaugurando um post didático inglês, queria falar sobre contextos e escopo no Javascript, sabendo que é uma das maiores dificuldades dos recém chegados à linguagem.

Vou tentar ser bem sucinto neste post e ser mais direto.

Praticamente sempre estamos dentro de uma execução de função no Javascript, portanto muitas vezes a keyword this aparece no código e muita gente não sacou ainda o porque dela aparecer e para que serve.

Mas quando ela aparece e por que?

Sempre dentro de uma função, por exemplo, em classes Javascript.

var Pessoa = {
	_class :function(nome){

		this.name = function(){
			return nome;
		}
	}

}

Neste código eu crio uma classe pessoa e passo pra ela o parâmetro “nome”.
Como a variável nome é privada, só existe dentro de _class, portanto precisamos usar alguma interface com o meio exterior, usando por exemplo uma variável pública, this.name, no caso.

Na maioria das linguagens orientadas a objeto você verá esta keyword, dizendo que aquela propriedade ou método é pública, portanto poderá ser acessada externamente, no nosso caso:

var Edu = new Pessoa._class('Eduardo');
Edu.name() // 'Eduardo'

Se não tivéssemos especificado que this.name é um método que retorna o valor da variável “nome” de forma alguma conseguiríamos resgatar o valor desta variável externamente.

Então, um papel da palavra this é definir propriedades ou método públicos para classes e definir o que pode ser acessado externamente.

MAAASSS, this está presente em todas as funções, geralmente se você criar uma função que não será uma classe, pode também acessar o escopo daquela função, através da keyword this:

function mensagem(){
	alert(this);
}

mensagem(); // object Window

Mesmo sem definir uma classe, a função mensagem possui a keyword this que te informa em qual contexto ela está sendo executada e quais propriedades e métodos você pode usar.

Este é um dos maiores poderes do Javascript na minha humilde opinião. Vou explicar o por que 😉

Esta função acima é executada no contexto do Window, que é o escopo do browser do navegador. Sempre que uma função é criada e não faz parte de uma propriedade ou método de um objeto recém criado, ela vai ser executada no contexto de window, no nosso caso, a função mensagem é uma função “solta”, não está atrelada à ninguém, portanto ela é executada no escopo Window.

Agora vou criar uma função atrelada à um objeto:


	var Pessoa = {
		nome :"",
		sobrenome : "",
		nome_completo :function(){ return this.nome + " " +this.sobrenome; }
	};

Aqui veja que não estou criando uma classe, estou criando um objeto único chamado Pessoa.
Ele tem as propriedades nome e sobrenome e possui uma função atrelada à este objeto, chamada “nome_completo”.

Vejam que usei novamente a keyword this para dizer que quero o nome e sobrenome deste objeto.
This em inglês significa “Isto, disto, deste, desta”. Portanto estou pedindo para resgatar o valor das variáveis deste objeto.

Se eu chamasse Pessoa.nome_completo() iria retornar uma string vazia, já que temos tanto a propriedade nome como sobrenome deste objeto vazias.

Mas, poderiamos preenchê-las:


	Pessoa.nome = "Eduardo";
	Pessoa.sobrenome = "Ottaviani";

	Pessoa.nome_completo(); // Eduardo Ottaviani	

Vejam que executo esta função usando como um método atrelado à variável Pessoa.
Agora, esta função foi executada no escopo de Pessoa, mas ela pode ser usada no escopo window também.
E é aqui que o Javascript pega com muitos programadores recém chegados à linguagem, porque se eu fizesse isso aqui:

var nome_completo = Pessoa.nome_completo;
nome_completo(); //undefined

Isto acontece porque você passou para a variável nome_completo apenas a função que está dentro da variável Pessoa.nome_completo. Você está executando a variável livre de um contexto, portanto, nome_completo() está sendo executada no contexto de window.

O resultado é undefined porque ele não sabe quem é nome e sobrenome dentro da função, porque Window não possui estas propriedades.

Vocês entenderam? Pessoa.nome_completo é uma função que é executada no escopo de Pessoa. Já nome_completo() é uma função que é excutada sem estar atrelada à um objeto, e por padrão ela é executada no escopo Window.

Vou mostrar agora um exemplo que pega a maioria das pessoas que ainda está aprendendo a linguagem, provavelmente você já passou por isto:

Pessoa.nome_completo = function(){

	setTimeout(function(){
		alert(this.nome + " " + this.sobrenome);
	}, 1000);

}

Eu redefini, o método público do objeto Pessoa, agora o que ele dá um alert na propriedade nome e sobrenome, após 1 segundo a partir da sua chamada.

Novamente ele exibe “undefined”, por que ?

Veja que estou executando uma função passada como parâmetro para a setTimeout que por sua vez executa a função sempre no escopo window. Toda função executada tanto pela setTimeout quanto pela setInterval é executada no contexto window.

Então como solucionamos este problema acima? Simples, basta guardar o escopo em uma outra variável, e usá-la dentro da função executada pela setTimeout:

Pessoa.nome_completo = function(){
	var _self = this;
	setTimeout(function(){
		alert(_self.nome +" "+ self.sobrenome);
	}, 1000);

}

_self é uma variável que criei para salvar o this que guarda o contexto do objeto Pessoa. Dentro de setTimeout a variável this tem outro valor, assume o contexto de window, então não pode ser usada para resgatar as propriedades de Pessoa, então usamos a variável _self para obtê-las. =D

Outro problema clássico que acontece com quem não tem muita experiência na linguagem, já vi muitos códigos assim:


<a href="#" id="link-0">Nome :Eduardo</a>
<a href="#" id="link-1">Sobrenome: Ottaviani</a>
<a href="#" id="link-2">Profissão : Programador</a>


var link = document.getElementsByTagName("a");

for(var i = 0; i < link.length; i++){
	link[i].onclick = function(){
		alert( document.getElementById("link-" + i).innerHTML );
	}
}

Este código pega os links da página e dá um alert em seu conteúdo. Simples assim.
Bom, antes de tudo, não poderia usar no alert isto: link[i].innerHTML. Se fizesse isso, o alert apenas mostraria o valor do último link. Isto é um outro problema que vou tratar em um post futuro.

Então o gaiato não sabendo como usar a keyword this, e não sabendo também deste bug que comentei acima, consegue resolver colocando id’s numéricos nos links e usando os indices do for para concatenar no nome dos id’s e assim retornar os valores dos reespectivos links.

Se você quer contratar um programador Javascript e quer testar seus conhecimentos, peça para que ele resolva este problema, isso mostra muito sobre seus conhecimentos ;).

Usando a keyword this, é muito fácil resolver esses problemas acima, basta usá-lo assim:

var link = document.getElementsByTagName("a");

for(var i = 0; i < link.length; i++){
	link[i].onclick = function(){
		alert( this.innerHTML );
	}
}

Agora, não precisamos usar id’s numéricos e nosso código funcionaria com qualquer link da página. Não seria necessário após montar o html, ficar dando id’s para todos os links né rapaziada?

Vejam, a função onclick está atrelada à variável link[], logo o contexto, o escopo desta função é o elemento html, e assim usamos a propriedade innerHTML que todos os elementos do DOM possuem.

Prometi que ia ser breve, mas Javascript é muito complexo, qualquer assunto é complicado de explicar.
Muita gente acha ruim passar por estes problemas de escopo no começo, mas como eu disse, é uma das maiores qualidades da linguagem.

Por que? Digamos que se eu tivesse outros objetos da mesma “forma” que possuem tanto nome e sobrenome em suas propriedades, teria de criar um método nome_completo em cada um deles que faz exatamente a mesma coisa, isso seria meio redundante.

As funções no Javascript herdam dois métodos, que comentei no blog já, .apply() e .call(). Eles permitem passar o contexto para a função!!!

Poderia então criar uma função genérica, que pode ser usada por um ou mais objetos, sem ter que criar uma propriedade igual a todas elas. \o/

Assim:

	var Edu = {
		nome : "Eduardo",
		sobrenome : "Ottaviani",
		idade : 28
	}

	var Gordo = {
		nome : "Jô",
		sobrenome : "Soares",
		peso :"Pesado"
	}

	var Guga = {
		nome : "Gustavo",
		sobrenome :"Kuerten",
		profissão :"Esportista",
		nacionalidade : "Brasileira"
	}

	function nome_completo(){
		return this.nome + " " + this.sobrenome;
	}

Vejam que os objetos são diferntes entre si, mas todos possuem a propriedade nome e sobrenome. Vejam que criei uma função livre chamada nome_completo(). Se eu a executasse, retornaria “undefined”, porque está sendo chamada no contexto de window.

Mas usando os métodos apply e call:

	nome_completo.call(Gordo); // "Jô Soares"
	nome_completo.apply(Guga); // "Gustavo Kuerten"
	nome_completo.call(Edu); // "Eduardo Ottaviani"

O primeiro parâmetro dos métodos .call() e .apply() definem em qual escopo a função deve ser executada, portanto, executamos a função nome_completo() e passamos para ela o escopo no qual ela deve ser executada. Dessa forma, se o objeto que mandamos para ela tiver as propriedades nome e sobrenome, ela vai retornar o valor destas propriedades concatenadas =D.

Isso eu considero genial na linguagem, saber isso te dá uma visão muito maior sobre reutilização de códigos e permite que faça inclusive heranças no javascript de forma muito simples e objetiva.

Já falei sobre Herança e os métodos Apply e Call antes.
Aconselho a ler estes posts novamente, talvez agora faça tudo mais sentido =).

Um grande abraço!

Anúncios

6 comentários sobre “What is this ?

    • Oi Nando, justamente o contrário, o this serve para acessar coisas dentro do escopo atual. O que deve se preocupar sempre é em que contexto ou escopo está, para poder usar o this corretamente. 😉

  1. Olá, tenho uma questão.
    var Pessoa = function(firstname, lastname){
    this.firstname=firstname;
    this.lastname=lastiname;
    var email = “”;
    this.getEmail = function() { return email; }
    this.setEmail = function(x) { email = x; }
    }
    var person = new Pessoa();
    person.email = ‘joaodasilva@gmail.com’; // é possível proibir o acesso direto a email?

    Neste código email tem 2 métodos de acesso, porém eu gostaria de saber se é possível proibir a atribuição direta de valor, obrigando somente a passagem pelos métodos getEmail e setEmail.

    • Olá Luciano, tudo bem?

      Desculpe a demora. Realmente você tem dois meios de resgatar essa informação e tenho que dizer que no javascript não há como proibir a atribuição que fez no seu objeto. Pode parecer uma coisa ruim, mas esse é um poder que vai te ajudar mais do que te atrapalhar na linguagem. Ruby também é assim, pode a qualquer momento atribuir um atributo à um objeto em tempo de execução.

      Isso é uma coisa boa! Não é ruim. Portanto, é interessante que documente muito bem suas classes, e uma vez que algum programador atribuir qualquer valor à um objeto, ele irá resgatar o valor errado, portanto, ele está ciente de que está usando a classe de forma incorreta.

      Quando utiliza a classe de terceiros, deve saber como ela funciona, e ao atribuir uma propriedade sem saber o que está fazendo, vai pagar pelo erro mais pra frente.

      []’s

  2. Parabéns pelo artigo. Fiquei com uma dúvida ainda, no caso do código abaixo, pode-se dizer que o acesso ao atributo foi restringido apenas ao escopo do objeto?

    function Pessoa(_email) {
    var email = _email;

    this.setEmail = function(_email) {
    email = _email;
    };

    this.getEmail = function() {
    return email;
    };
    }

    • Olá Gabriel, eu não entendi sua pergunta…

      No caso do seu código, a variável _email usada como parametro da sua função Pessoa pode ser vista por qualquer função criada dentro da função pessoa. E ainda, a variável _email da setEmail é diferente da variável _email de Pessoa. Então o seu código pode ficar confuso dessa forma… eu reescreveria assim:

      function Pessoa(email) {

      this.setEmail = function(_email) {
      email = _email;
      };

      this.getEmail = function() {
      return email;
      };
      }

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s