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!