Desenvolvimento

27 jun, 2016

ECMAscript 6: let, const e block bindings

Publicidade

Na maioria das linguagens, variáveis ou bindings são criados no lugar onde a declaração ocorre. No JavaScript, temos um comportamento um pouco diferente, os lugares onde as variáveis são criadas depende de como foram declaradas, por exemplo:

function testVar(booleanEx) {

  if (booleanEx) {
    var varDeclaration = "algum valor";

    return varDeclaration;
  } else {

    //varDeclaration existe aqui como undefined
    return null;
  }

  //também existe aqui como undefined
}

Esse tipo de comportamento pode trazer problemas então o Ecmascript6 adicionou novas formas para melhorar o controle do escopo das variáveis.

Caso você não tenha lido ainda o meu artigo sobre Hoisting agora é uma ótima hora para entender
os impactos que ele pode causar.

Block level declarations

Quando uma variável é inacessível fora de um block scope (também conhecido como lexical scope) onde foi declarada, ela é classificada como uma block level declaration. Normalmente, esse cenário ocorre em:

  • Funções
  • Dentro de um loop

Usando let

O let foi criado para ter o mesmo comportamento esperado quando usando var, ou seja, não fazer hoisting e ser limitado ao escopo onde foi declarado, além de outras diferenças que vamos ver na sequência. Como o let não faz hoisting, uma boa prática é declarar sempre no topo do escopo no qual iremos usá-lo.

function testLet(booleanEx) {

  if (booleanEx) {
    let letDeclaration = "some value";

    return letDeclaration;
  } else {

    //letDeclaration não existe aqui
    return null;
  }

  //também não existe aqui
}

Prevenção de redeclaração

Caso tentemos re-declarar um identificador que já foi declarado no mesmo escopo, vamos receber um erro como este:

var test = "exemplo";

//Syntax error
let test = "testando";

Essa regra muda quando declaramos um let com mesmo nome em um escopo interno, por exemplo:

var test = "valor inicial";

if(someCondition) {
  let test = "algum valor";
}

Dentro do if foi criado um novo let, que dentro desse escopo sobrescreveu o valor anterior de var; fora do escopo, a variável test ainda terá como valor o “valor inicial”.

Usando const

Também temos const no ES6. Constants não podem ter seu valor alterado, uma vez que ele foi setado. Com base nisso, temos que sempre declarar constants que recebem algum valor, como no exemplo a seguir:

const test = "algum valor";

//vai dar erro por que não tem valor
const test2;

É importante salientar que esse comportamento não vale para objetos – é possível alterar os valores de um objeto mesmo ele tendo sido declarado como const:

const usuario = {
    nome: "Waldemar"
};

// funciona
 usuario.nome = "Outro nome";

// vai dar erro
 usuario = {
     nome: "Novo nome"
 };

const vs let

const e let são block level declarations e dividem praticamente todos os mesmos comportamentos, exceto pelo fato de que constants não podem ter seu valor alterado, uma vez que já foram iniciadas.

Block bindings em loops

Problemas com escopo em loops assombram os desenvolvedores JavaScript desde sempre. Vamos ver um exemplo triste:

for (var i = 0; i < 10; i++) {
    //faz algo com i
}

 // i ainda é acessível e tera o valor de 10
 console.log(i);

Por conta do hoisting, a variável segue acessível, mesmo fora do escopo do loop para o qual ela foi criada. Usando let, conseguimos ter o comportamento esperado:

for (let i = 0; i < 10; i++) {
    //faz algo com i
}
 // i não existe aqui
 console.log(i);

Funções em loops

Seguindo nosso bullying com vars, funções que recebem vars como parâmetro dentro de loops apresentam um comportamento interessante também, vamos ver:

var funcs = [];

for (var i = 0; i < 10; i++) {
    funcs.push(function() { console.log(i); });
}

funcs.forEach(function(func) {
    func();     // o resultado sera 10 nas 10 vezes
});

Jovens, pensaram que o valor de saída seria sequencial de 0 até 9, né? E na verdade foi 10 vezes o número 10 que apareceu, né? Isso acontece porque a variável “i” é compartilhada entre cada interação do loop, então as funções mantêm a referência da mesma variável.

Usando let em loops

Para manter nossa sanidade mental, o ES6 adicionou um comportamento especial para let (não relacionado a hoisting) quando for interagir com um loop: cada interação cria uma nova variável “i”, e cada função agora recebe uma própria copia de “i”:

var funcs = [];

for (let i = 0; i < 10; i++) {
    funcs.push(function() { console.log(i); });
}

funcs.forEach(function(func) {
    func();     // o resultado sera 0,1,2,3....9
});

Usando const em loops

Como dá pra imaginar, em um loop precisamos incrementar o valor da variável que está declarada na interação, o que significa que constants vão dar erro na segunda interação, correto?

//vai dar erro na segunda interação
for (const i = 0; i < 10; i++) {
  //faz algo
}

Porém, existem certos tipos de loops nos quais não necessariamente incrementamos o valor de uma variável, e sim o extraímos de uma lista, como fazem os loops for in e for of:

var obj = {
  a: "valor",
  b: "valor2",
  c: "valor3"
}

//vai mostrar a depois b depois c
for(const key in obj) {
  console.log(key);
}

for in e for of  criam um novo binding para cada interação. A única diferença aqui é que key não pode ter seu valor alterado, pois é uma constant. 

Boas práticas

Depois de ver as diferenças entre var, let e const, a primeira coisa que vem em mente é trocar var por let. Isso é justo, mas a comunidade trouxe um ponto de vista interessante: usar const em tudo que é declaração, e só usar let quando realmente for necessário sobrescrever um valor. Inclusive podemos colocar regra no nosso eslint para dar erro quando uma variável estiver sendo declarada como let e não estiver sendo sobrescrita.

Versão em vídeo:

Valeu, galera, até a próxima!