:::: MENU ::::

ECMAscript 6: let e const e block bindings

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, porexemplo:

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 sequencia. Como o let não faz hoisting uma boa prática é declarar sempre no topo do escopo no qual iremos usa-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 ja foi declarado no mesmo escopo, vamos receber um erro como esse:

var test = "exemplo";

//Syntax error
let test = "testando";

Essa regra muda quando declaramos um let com mesmo nome em um escopo interno, como 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 o mesmo foi setado. Baseado 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 numero 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” , 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 da 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
}

Porem existem certos tipos de loops onde não necessariamente incrementamos o valor de uma variável e sim extraímos ele 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 de var pra 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 esta sendo declarada como let e não esta sendo sobrescrita.

Versão em video

Valeu galera, até a próxima!

 


  • lrlessa

    xdb o post waldecir, o kyle simpson fez um post opinativo sobre o uso do var e do const que vale a pena dar uma lida: https://davidwalsh.name/for-and-against-let

    • Waldemar Neto

      Massa mesmo leo, inclusive o lance do temporal dead zone (TDZ) que pretendo fazer um post só pra isso.
      Mas aproveitando o post do Kyle que tu mostrou, que ele falou dos loops e tal eu e um amigo descobrimos que o comportamento novo de let é bem menos performático pois ele vai instanciar um novo let a cada interação. Se tu quiser ir um pouco mais a fundo no assunto tem um PR aqui https://github.com/iefserge/eshttp/pull/9 que mostra o benchmarking.
      Abraço

      • lrlessa

        Pelo PR parece que independente de ser um for ou não o let ainda não tá otimizado no v8 (nem o const), mas provavelmente é temporário, no front usando um compilador ainda tá tranquilo, no server-side mesmo assim acho que dá pra usar já que vão melhorar num futuro próximo (espero). Haha.
        Abração

  • Van Neves

    Primeiramente parabéns pelo texto, muito bom para a galera que está aprendendo ES6. Mas tenho uma observação:

    “Também podemos declarar variáveis como const no ES6.” Variável é variável, que varia, que muda, já a constante é fixa, que não muda. O correto seria “Também podemos declarar constantes como const no ES6.”