Closure ? Hoisting ? Instanciação usando uma IIFE? WTF ?

Hoisting

Não precisa ser um gênio para entender. Muitos que não sabem seu significado pode está pensando “Nossa, deve ser algo difícil!”, mas na verdade precisa mais de atenção, a linguagem usa e você não tá percebendo, porém pode causar bastante confusão se não souber da sua existência, por ter comportamentos ‘ocultos’. Então o que significa Hoisting ? Traduzindo para português, seria ‘elevação’, e isso tem todo sentido no JavaScript. Antes de mostrar um exemplo com hoisting, é importante entender como funciona a declaração de uma variável no javascript.

// Exemplo 1:
function getNome(){
  var nome = "victor";
  return nome;
}//--> victor

function getCachorroNome(){
  var nome = "Calango JR.";
  return nome;
}//--> Calango JR.

Exemplo 1 A palavra reservada var especifica que a variável seja do escopo atual, com isso, usando a variável nome em outras funcoes não irá atrapalhar, pois ela apenas faz parte do seu escopo.

//Exemplo 2:
var cachorro;
function setCachorroNome(){
  cachorro=nome;
}
function modificaNome(){
  cachorro='spike';
  setNomeCachorro("calango");
  modificaNome();
  console.log(cachorro);
  //--> Calango JR.
}

Exemplo 2 Repare que damos um nome de calango, mas a funcao modificaNome( ) altera o nome da variável, isso acontece por ela ser uma variável global, um conceito importante que ainda veremos melhor.

//Exemplo 3:
function imprimeCachorro(){
  console.log(cachorro);
}
imprimeCachorro();
//ReferenceError: a is not defined

O erro acontece pois não declaramos nenhum cachorro, então vamos lá =)

//Exemplo 4:
var cachorro = 'dog';
function imprimeCachorro(){
  console.log(cachorro);
  var cachorro='spike';
}
imprimeCachorro();//-->underfine

Retorna underfine por ter uma variável no seu escopo imprimeCachorro( ) declarada, porém não instanciada. Você pode tá se perguntando ‘Como não foi instanciada se antes da função eu defino o cachorro recebendo dog e dentro da função defino cachorro recebendo spike ?’

A lógica de muitas linguagens seria mostrar ‘dog’, pois já foi instanciada no topo. Taí um dos porquês que você precisa estudar a linguagem como funciona e não apenas sua sintaxe, JavaScript diferente de algumas linguagens, tem seus comportamentos diferenciado, e se você já estudou C, vai perceber que a sua ação é diferente. E é aí amiguinho que a mágica acontece, a variávei foi ‘hoisteada’, elevada para o topo. Isso acontece pois o compilador na verdade, declara todas as variáveis, logo que seu código começa a ser compilado.

Como acontece? ⬇

//Exemplo 5:
var cachorro;
//underfine -compilador elevando
cachorro = 'dog'
function imprimeCachorro(){
//var cachorro; compilador elevando
  console.log(cachorro);
  cachorro = 'spike';
}
ImprimeCachorro();//-->underfine

Exemplo 5 Logo depois ele executará a funcao imprimeCachorro( ), e entao ele pula as declarações e começa a executar o console.log(cachorro), porém não houve nenhuma atribuição ao cachorro dentro do imprimeCachorro( ), já que foi declarada e hoisteada, e com isso ele continuou com o valor underfine e imprimiu.

Com isso, já sabemos o que significa o underfine quando aparecer.

A mesma coisa pode ser aplicada para uma função.

//Exemplo 6:
function getQualquerValor(){
  function pegaValor(){
    return 0;
  }
  return pegaValor();
  function pegaValor(){
    return 1;
  }
}
var valor  = getQualquerValor();
console.log(valor);//--> 1

Exemplo 6 Acontece que as duas funções pegaValor( ) foram jogadas para o topo, e em seguida é retornada a última pegaValor( ), pois ela sobrescreve a primeira.

Como acontece ? ⬇

//Exemplo 6:
var valor;
//underfine - Elevada para o topo
function getQualquerValor(){
//continua no topo
  function pegaValor(){
  //elevada para o topo
    return 0;
  }
   function pegaValor(){
    return 1;
  }
  return pegaValor();
  //prevalece a última
}
valor  = getQualquerValor(); //--> 1

E agora ? Vai ser o mesmo resultado ?

//Exemplo 7:
function getQualquerValor(){
  var pegaValor = function(){
    return 0;
  }
  return pegaValor();
  var pegaValor = function(){
    return 1;
  }
}
var valor  = getQualquerValor();
console.log(valor);//--> 0

Agora cuidado, estamos usando uma funções anônima, com isso a declaração sobe e as funções (atribuição) continuam no mesmo lugar, e assim prevalecendo sempre a de cima.

Como acontece ? ⬇

//Exemplo 8:
var valor;
//underfine - elevada para o topo
function getQualquerValor(){
  var pegaValor = function(){
    return 0;
  }
  return pegaValor();
  var pegaValor = function(){
    return 1;
  }
}
valor  = getQualquerValor();
console.log(valor);//--> 0

Closures

Traduzindo closure, podemos dizer que é algo de encerramento, no sentido de guardar, pôr em um lugar fechado. JavaScript não é a única a usar essa poderosa técnica, ela veio das linguagens funcionais, mas que acabou difundindo e implementado em outras linguagens como C#.

Um exemplo comum ⬇

//Exemplo 9:
function somador(n1){
  return function(n2){
  return n1+n2;
  }
}

Mas cuidado, o somador não é uma closure, e sim o seu valor que retorna.

var closureSoma2 = somador(2);
var total  = closureSoma2(5);
console.log(total);//--> 7

Outro exemplo ಠ_ಠ

//Exemplo 10:
function mostrarMensagem(){
  var mensagem = "Cuidado, clojure";
  function tela(){
    console.log(mensagem);
  }
  tela();//considerado uma closure
}

Exemplo 10 Note que ao chamar a tela, ele usa a variável mensagem, que está no escopo mostrarMensagem( ), a função de dentro (inner), vai ter o acesso as variaveis da funcao de fora (outer).

var closureSoma2 = somador(2);
var total  = closureSoma2(5);
console.log(total);//--> 7

Mais outro exemplo, e se reclamar vem mais outro ლ(ಠ▃ಠლ)

//Exemplo 11:
function contar(n){
  var cont = n;
  return{
    incremente:function(){
      cont++;
  //incrementa o valor passado por parametro
  //lembre, ele está no escopo de fora
    },
    get:function(){
      return cont;//pega o valor atual
    }
  }
}
var contador = contar(0);
contador.incremente();
contador.incremente();
console.log(contador.get());//--> 2

Na prática ⬇

Primeiro vamos imaginar que precisamos saber quantas vezes o usuário está clicando no botão. Se você entendeu mesmo, não vai querer usar uma variável global pra sair contando, isso só vai te trazer prejuízo na frente.

//Exemplo 12:
var butao = document.getElementById('button');
 button.onclick = (function(e){
  cont++;//contando
  if(cont == 1){
    alert("Aguarde um momento");
  }else
    if(cont == 2){
      alert("Eu falei pra esperar!");
    }else
      if(cont == 3){
        alert("Fica na sua quieto e aguarde!"
    }else{
    alert("Chato!");
    }
 };
})();//cria e executa

"Todos os seres humanos têm três vidas: a pública, a privada, e a secreta."

Gabriel García Márquez


Não podemos ficar criando variáveis globais, pois elas ficam livre ao longo do código e provavelmente pode-se modificar e trazer dor de cabeça. Através de closure, podemos deixar ela escondida, ou seja, privada. Precisamos usar encapsulamento, um conceito das linguagens orientada a objetos, ora mas JavaScript é orientada a objetos ? NÃO. Porém, todavia, JS é linda, podemos criar um mecanismo de privar uma variável usando closure, e se você prestar bem atenção, não vai ter nenhuma dúvida. Vamos criar um exemplo clássico, um banco onde posso depositar e sacar, claro que quero meu dinheiro privado.

//Exemplo 13:
function banco(){
  var saldo = 0;
  var cofre = {};
  function sacar(e){
  if(saldo == 0){
    console.log("Saldo: 0");
    return 0;
  }else if(saldo - e < 0){
 console.log("Saldo insuficiente para o saque");
 return 0;
    }
    saldo = saldo - e;
    console.log("Novo saldo: "+saldo);
    return e;
  }
  function depositar(e){
   saldo += e;
   console.log("Depositado com sucesso!");
  }
   cofre.sacar = sacar;
   cofre.depositar = depositar;
   return cofre;
}
var banco();
p.depositar(100);//Depositado com sucesso!
p.sacar(20);//Novo saldo: 80

Boas práticas: (⌒‿⌒)

Em projetos grande um dos problemas que acontecem muito é o de comportamento, alguns por está conseguindo acessar variáveis que não deviam, vazando escopo, sempre feche o escopo, se organize e não fique criando variáveis globais.

Variáveis Globais

Falamos bastante sobre não criar variáveis globais, e que isso pode lhe trazer dores de cabeça lá na frente, mas por quê ? Elas podem ser acessadas por qualquer escopo, e possibilitando as chances de você modificar ou usar o mesmo nome.

1.

//Exemplo 14:
var mensagem = 'sou global';
function alteraMensagem(){
  var mensagem = 'não sou global';
}
alterarMensagem();
console.log(mensagem);//sou global

2.

//Exemplo 15:
var mensagem = 'sou global';
function alteraMensagem(){
   mensagem = 'modifiquei';
}
alterarMensagem();
console.log(mensagem);//sou global

Exemplo 14/15 Na exemplo 2 a função alteraMensagem, não foi criado nenhuma variável, apenas atribuição, por isso ela modificou, diferente do exemplo 1, onde tem uma palavra reservada var, onde indica uma declaração no escopo atual.

Variáveis por parâmetro

É comum demais passarmos parâmetros pela nossas funções, porém no JavaScript não precisamos especificar que tipo de dados vamos receber.

//Exemplo 16:
var canguru = 1;
function pular(e){
   canguru += e;
}
pular(4);
console.log(canguru);//--> 5

Um das coisas curiosas é que ele não verifica nossos argumentos para dar erro de sintaxe. Como por exemplo:⬇

//Exemplo 17:
function somar(n1,n2){
  return n1+n2;
}
var resultado = somar(1);
console.log(canguru);//--> NaN

Mas por quê ?

Simples, como não foi passado por parâmetro, ela é setada como underfined, e você não pode querer somar um número com underfined.

E quando existe mais parâmetros do que o esperado ?

Sem problemas, vamos ver outro exemplo ٩(͡๏̯͡๏)۶

//Exemplo 18:
function multiplica(n1,n2){
  return n1*n2;
}
var num1 = 5;
var num2 = 10;
var num3 = 6;
var total = multiplica(num1, num2, num3);
console.log(total);//--> 50

Preciso nem explicar o que houve. (ړײ

Instanciação usando uma IIFE

IIFE é comumente chamado de “fechamento” ou envoltório.

Por que usar isso ?

Variáveis globais é um enorme risco, onde pode ocorrer colisões de nomes e trazendo grandes consequências e possívelmente um dos problemas mais difícieis de detectar. Com JavaScript é apenas uma questão de adequação, você declara dentro de um escopo local para que ela não fique jogada no escopo global, e claro, nunca esquecer da palavra-chave var, pois sem ela, a variável é global.

//Exemplo 19:
function somar(n1,n2){
  return n1+n2;
}
var resultado = somar(1);
console.log(canguru);//--> NaN

Mas por quê ?

Simples, como não foi passado por parâmetro, ela é setada como underfined, e você não pode querer somar um número com underfined.

Vamos ver outro exemplo ٩(͡๏̯͡๏)۶

//Exemplo 20:
var n = 3;
getValor = (function(){
  return e;
}(n));
n = 4;
var valor = getValue();
console.log(valor);//--> 3

Cuidado ao pensar que seja 4, ele chama logo preservando o contexto e retornando o argumento.

Nesse exemplo sim ⬇ ele é chamado apenas depois da atribuição no n.

//melhorando o exemplo 20:
var n, getValue;
n = 1;
getValue =function(){
  return v;
};
n = 2
var valor = getValue();
//perceba que já houve mudança
console.log(valor);//--> 2

Conclusão

Dessa forma aprendemos vários conceitos, todos eles são importantes, e alguns possuem até livros específicos como o de Closure: The Definitive Guide, então nunca descarte de sempre estudar.

Bibliografia