Nesta postagem, examinarei o caso a favor (e talvez contra?) de um dos novos recursos que virão no JavaScript ES6: o let
palavra-chave. let
permite uma nova forma de escopo não acessível anteriormente aos desenvolvedores de JS em geral: escopo de bloco.
Escopo da função
Vamos revisar brevemente os conceitos básicos de escopo de função – se o senhor precisar de uma cobertura mais aprofundada, consulte meu “You Don’t Know JS: Scope & Closures” (O senhor não conhece JS: escopo e fechamentos) que faz parte do livro “You Don’t Know JS” (O senhor não conhece JS) série de livros.
Considere:
foo(); // 42 function foo() { var bar = 2; if (bar > 1 || bam) { var baz = bar * 10; } var bam = (baz * 2) + 2; console.log( bam ); }
O senhor já deve ter ouvido o termo “içamento” para descrever como a JS var
são tratadas dentro dos escopos. Não se trata exatamente de uma descrição técnica de como isso funciona, mas sim de uma metáfora. Mas, para nossos propósitos aqui, é suficiente para ilustrar. O trecho acima é essencialmente tratado como se tivesse sido escrito assim:
function foo() { var bar, baz, bam; bar = 2; if (bar > 1 || bam) { baz = bar * 10; } bam = (baz * 2) + 2; console.log( bam ); } foo(); // 42
Como o senhor pode ver, o foo()
foi movida (também conhecida como “hoisted”, também conhecida como lifted) para o topo de seu escopo e, da mesma forma, a declaração da função bar
, baz
, e bam
variáveis foram elevadas ao topo de seu escopo.
Como as variáveis JS sempre se comportaram dessa maneira, muitos desenvolvedores optam por colocar automaticamente suas variáveis var
no topo de cada escopo (função), de modo a combinar o estilo do código com seu comportamento. E essa é uma maneira perfeitamente válida de fazer as coisas.
Mas o senhor já viu algum código que faz isso, mas que também faz coisas como essa no mesmo código:
for (var i=0; i<10; i++) { // .. }
Isso também é extremamente comum. Outro exemplo que é bastante comum:
var a, b; // other code // later, swap `a` and `b` if (a && b) { var tmp = a; a = b; b = tmp; }
O var tmp
dentro da if
viola um pouco o estilo de codificação ostensivo “mover todas as declarações para o topo”. O mesmo acontece com o bloco var i
na seção for
no trecho anterior.
Em ambos os casos, as variáveis serão “içadas” de qualquer forma, então por que os desenvolvedores ainda colocam essas declarações de variáveis mais profundamente no escopo em vez de no topo, especialmente se todas as outras declarações já foram movidas manualmente?
Escopo de blocos
O motivo mais importante é que os desenvolvedores (muitas vezes instintivamente) querem alguns variáveis para agir como se pertencessem a uma seção menor e mais limitada do escopo. Em termos específicos, há casos em que queremos definir o escopo de uma declaração de variável para o bloco ao qual ele está associado exclusivamente.
No for (var i=..) ..
é quase universal que o desenvolvedor pretenda que o i
seja usado somente para os fins desse loop, e não fora dele. Em outras palavras, o desenvolvedor está colocando o var i
declaração em o for
para sinalizar estilisticamente a todos os outros – e ao seu futuro eu! — que o i
pertence ao for
loop only. O mesmo acontece com o var tmp
dentro desse if
declaração. tmp
é uma variável temporária e só existe para os fins dessa declaração. if
bloco.
Estilisticamente, estamos dizendo: “não use a variável em nenhum outro lugar a não ser aqui”.
Princípio do menor privilégio
Há uma engenharia de software chamada “princípio do menor privilégio (ou exposição)”, que sugere que o design adequado do software oculta detalhes, a menos e até que seja necessário expô-los. Geralmente fazemos isso exatamente isso no design do módulo, ocultando variáveis e funções privadas dentro de um fechamento e expondo apenas um subconjunto menor de funções/propriedades como a API pública.
O escopo de blocos é uma extensão dessa mesma mentalidade. O que estamos sugerindo é que o software adequado coloque as variáveis o mais próximo possível, e o mais abaixo possível no escopo/bloqueio, de onde elas serão usadas.
O senhor já conhece instintivamente esse princípio exato. O senhor já sabe que não tornamos todas as variáveis globais, mesmo que em alguns casos isso seja mais fácil. Por quê? Porque é um projeto ruim. É um projeto que levará a colisões (não intencionais), que resultará em bugs.
Portanto, o senhor coloca suas variáveis dentro da função pela qual elas são usadas. E quando aninha funções dentro de outras funções, o senhor aninha variáveis dentro dessas funções internas, conforme necessário e apropriado. E assim por diante.
O escopo do bloco simplesmente diz que eu quero poder tratar um { .. }
bloco como um escopo, sem precisar criar uma nova função para encapsular esse escopo.
O senhor está seguindo o princípio ao dizer: “Se eu for usar apenas i
para este for
vou colocá-lo diretamente na seção for
definição do loop”.
JS Missing Block Scoping
Infelizmente, historicamente, a JS não tem nenhuma maneira prática de impor esse estilo de escopo, portanto, cabe ao melhor comportamento respeitar o estilo que está sendo sinalizado. É claro que a falta de aplicação significa que essas coisas são violadas e, às vezes, não há problema, enquanto outras vezes isso gera bugs.
Outras linguagens (por exemplo, Java, C++) têm verdadeiros escopo de blocoem que o senhor pode declarar uma variável como pertencente a um bloco específico em vez de ao escopo/função circundante. Os desenvolvedores dessas linguagens conhecem bem os benefícios de usar o escopo de bloco para alguns de suas declarações.
Muitas vezes, eles acham que o JS não tem capacidade expressiva por não ter uma maneira de criar um escopo inline dentro de um { .. }
em vez da definição de função inline mais pesada (também conhecida como IIFE – Immediately Invoked Function Expression).
E eles estão totalmente certos. O JavaScript está perdendo o escopo de blocos. Especificamente, está faltando uma forma sintática de aplicar o que já estamos acostumados a expressar de forma estilística.
Nem tudo
Mesmo em linguagens que têm escopo de bloco, nem toda declaração de variável acaba tendo escopo de bloco.
Pegue qualquer base de código bem escrita de uma linguagem desse tipo e o senhor certamente encontrará algumas declarações de variáveis que existem no nível da função e outras que existem em níveis de blocos menores. Por quê?
Porque esse é um requisito natural de como escrevemos software. Às vezes, temos uma variável que vamos usar em todos os lugares da função e, às vezes, temos uma variável que vamos usar apenas em um lugar muito limitado. Certamente não é uma proposta de tudo ou nada.
Prova? Parâmetros da função. Essas são variáveis que existem para todo o escopo da função. Que eu saiba, ninguém defende seriamente a ideia de que as funções não deveriam ter parâmetros nomeados explícitos porque não teriam “escopo de bloco”, porque a maioria dos desenvolvedores sensatos sabe o que estou afirmando aqui:
O escopo de bloco e o escopo de função são ambos válidos e úteis, não apenas um ou outro. Esse tipo de código seria muito bobo:
function foo() { // <-- Look ma, no named parameters! // .. { var x = arguments[0]; var y = arguments[1]; // do something with `x` and `y` } // .. }
É quase certo que o senhor não escreveria um código como esse apenas para ter uma mentalidade de “apenas escopo de bloco” sobre a estrutura do código, assim como não teria x
e y
sejam variáveis globais em uma mentalidade de “escopo global apenas”.
Não, o senhor apenas nomearia o x
e y
e use-os em qualquer parte da função que o senhor precisar.
O mesmo se aplica a qualquer outra declaração de variável que o senhor possa criar e que pretenda e precise usar em toda a função. O senhor provavelmente colocaria apenas um var
na parte superior da função e seguir em frente.
Apresentando let
Agora que o senhor entende por que o escopo do bloco é importante e é importante engoliu a verificação de sanidade de que ele altera o escopo de função/global em vez de substituí-lo, podemos ficar animados com o fato de que o ES6 é finalmente introduzindo um mecanismo direto para a definição do escopo do bloco, usando o let
palavra-chave.
Em sua forma mais básica, let
é um irmão de var
. Mas as declarações feitas com let
têm escopo para os blocos em que ocorrem, em vez de serem “içadas” para o escopo da função anexa, como o var
s fazem:
function foo() { a = 1; // careful, `a` has been hoisted! if (a) { var a; // hoisted to function scope! let b = a + 2; // `b` block-scoped to `if` block! console.log( b ); // 3 } console.log( a ); // 1 console.log( b ); // ReferenceError: `b` is not defined }
Eba! let
as declarações não apenas expressam, mas também aplicam o escopo do bloco!
Basicamente, qualquer lugar em que ocorra um bloqueio (como um { .. }
), um let
pode criar uma declaração com escopo de bloco dentro dele. Portanto, sempre que o senhor precisar criar declarações de escopo limitado, use let
.
Nota: Sim, let
não existe antes do ES6. Mas existem alguns transpiladores de ES6 para ES5, por exemplo: traceur, 6to5e Continuidade — que pegará seu ES6 let
(juntamente com a maior parte do restante do ES6!) e o converterá em código ES5 (e, às vezes, ES3) que será executado em todos os navegadores relevantes. O “novo normal” no desenvolvimento de JS, dado que o JS começará a evoluir rapidamente em uma base de recurso por recurso, é usar esses transpiladores como parte padrão do seu processo de compilação. Isso significa que o senhor deve começar a criar no melhor e mais recente JS agora mesmoe deixar que as ferramentas se preocupem em fazer com que isso funcione em navegadores (mais antigos). O senhor não deve mais abrir mão de novos recursos de linguagem por anos até que todos os navegadores anteriores desapareçam.
Implícito vs Explícito
É fácil se perder na empolgação da let
que é um escopo implícito . Ele seqüestra um bloco existente e acrescenta ao propósito original desse bloco também a semântica de ser um escopo.
if (a) { let b = a + 2; }
Aqui, o bloco é um if
mas o let
simplesmente estar dentro dele significa que o bloco também se torna um escopo. Caso contrário, se o let
não estivesse lá, o { .. }
bloco não é um escopo.
Por que isso é importante?
Em geral, os desenvolvedores preferem mecanismos explícitos em vez de mecanismos implícitosporque geralmente isso torna o código mais fácil de ler, entender e manter.
Por exemplo, no âmbito da coerção de tipo JS, muitos desenvolvedores preferem um coerção explícita sobre um coerção implícita:
var a = "21"; var b = a * 2; // <-- implicit coercion -- yuck :( b; // 42 var c = Number(a) * 2; // <-- explicit coercion -- much better :) c; // 42
Nota: Para ler mais sobre esse tópico secundário de coerção implícita/explícita, consulte meu “You Don’t Know JS: Types & Grammar” (O senhor não conhece JS: tipos e gramática) livro, especificamente Capítulo 4: Coerção.
Quando um exemplo mostra um bloco com apenas uma ou poucas linhas de código dentro dele, é bastante fácil ver se o bloco tem escopo ou não:
if (a) { // block is obviously scoped let b; }
Mas em cenários mais reais, muitas vezes um único bloco pode ter dezenas de linhas de código, talvez até cem ou mais. Deixando de lado a preferência/opinião de que esses blocos não deveriam existir – eles do, é uma realidade – se o let
estiver enterrado bem no meio de todo esse código, ele se torna muito mais difícil saber se um determinado bloco tem escopo ou não.
Por outro lado, se o senhor encontrar um let
em algum lugar do código, e quiser saber a qual bloco ela pertence, em vez de simplesmente fazer uma varredura visual para cima até a declaração mais próxima de function
mais próxima, o senhor agora precisa examinar visualmente a palavra-chave {
que abre a chave. Isso é mais difícil de fazer. Não é muito mais difícil, mas difícil mesmo assim.
É um imposto um pouco mais mental.
Riscos implícitos
Mas não se trata apenas de um imposto mental. Considerando que var
as declarações são “içadas” para o topo da função anexa, let
as declarações não são tratadas como tendo sido “içadas” para o topo do bloco. Se o senhor acidentalmente tentar usar uma variável com escopo de bloco no bloco anteriormente do que onde sua declaração existe, o senhor receberá um erro:
if (a) { b = a + 2; // ReferenceError: `b` is not defined // more code let b = .. // more code }
Nota: O período de “tempo” entre a abertura {
e onde o let b
é tecnicamente chamado de “Zona Morta Temporal” (TDZ) – não estou inventando isso! — e as variáveis não podem ser usadas em sua TDZ. Tecnicamente, cada variável tem sua própria TDZ, e elas meio que se sobrepõem, novamente desde a abertura do bloco até a declaração/inicialização oficial.
Como já havíamos colocado o let b = ..
mais abaixo no bloco e, depois, queríamos voltar e usá-la mais cedo no bloco, temos um risco – uma arma de pé – em que esquecemos que precisávamos encontrar o let
e movê-la para o primeiro uso da palavra-chave b
variável.
É muito provável que os desenvolvedores sejam mordidos por esse “bug” do TDZ e acabem aprendendo com essa experiência ruim a sempre colocar o let
na parte superior do bloco.
E há outro perigo a ser implicado let
o perigo da refatoração.
Considere:
if (a) { // more code let b = 10; // more code let c = 1000; // more code if (b > 3) { // more code console.log( b + c ); // more code } // more code }
Digamos que, mais tarde, o senhor perceba que o if (b > 3)
parte do código precisa ser movida para fora do if (a) { ..
por qualquer motivo. O senhor percebe que também precisa pegar o let b = ..
para seguir em frente.
Mas o senhor não percebe imediatamente que o bloco se baseia em c
porque ele está um pouco mais escondido no código, e que o c
tem escopo de bloco para o if (a) { ..
. Assim que o senhor mover o bloco if (b > 3) { ..
o código é interrompido e o senhor precisa encontrar o bloco let c = ..
e descobrir se ele pode se mover, etc.
Eu poderia continuar apresentando outros cenários – hipotéticos, sim, mas também extremamente informados por muita experiência, não apenas com o meu próprio código, mas também com o código de outras pessoas no mundo real – mas acho que o senhor entendeu o ponto. É muito fácil cair nessas armadilhas perigosas.
Se houvesse escopos explícitos para o b
e c
, provavelmente teria sido um pouco mais fácil descobrir qual refatoração é necessária, em vez de ficar tropeçando para descobrir implicitamente.
Explícito let
Escopo
Se eu tiver convencido o senhor de que o implícito natureza do let
pode ser um problema/perigo – se o senhor não for extremamente cuidadoso, assim como todos os outros desenvolvedores que trabalharem no seu código! — Então, qual é a alternativa? Evitamos totalmente o escopo de bloco?
Não! Há maneiras melhores.
Em primeiro lugar, o senhor pode se forçar a adotar um estilo/idioma que não apenas coloca sua let
no topo do escopo, mas também que crie um bloco explícito para esse escopo. Por exemplo:
if (a) { // more code // make an explicit scope block! { let b, c; // more code b = 10; // more code c = 1000; // more code if (b > 3) { // more code console.log( b + c ); // more code } } // more code }
O senhor verá que aqui eu criei um { .. }
e coloquei o par let b, c;
logo no início, até mesmo na mesma linha. Estou deixando o mais claro e explícito possível que esse é um bloco de escopo e que ele contém b
e c
.
Se, posteriormente, eu precisar mover algum b
e for procurar o escopo combinado para o b
e c
não é apenas mais fácil de reconhecer, mas mais fácil de realizar, que eu posso mover todo o { let b, c; .. }
bloco com segurança.
Isso é perfeito? Claro que não. Mas é melhore tem menos riscos e menos impostos mentais (mesmo que pouco) do que o implícito do estilo/idiomas anteriores. Peço a todos os senhores que, ao começarem a usar let
considerem e prefiram uma abordagem mais explícito sobre o implícito forma.
Sempre explícito?
Na verdade, eu diria que ser explícito é tão importante que a única exceção que encontrei a essa “regra” é que eu gosto e uso for (let i=0; .. ) ..
. É discutível se isso é implícito ou explícito. Eu diria que é mais explícito do que implícita. Mas talvez não seja tão explícito como { let i; for (i=0; ..) .. }
.
Na verdade, há uma razão muito boa para isso for (let i=0; ..) ..
poderia ser melhor. Ele está relacionado a fechamentos de escopo, e é muito legal e poderoso!
{ let i; for (i=1; i<=5; i++) { setTimeout(function(){ console.log("i:",i); },i*1000); } }
Esse código, assim como seu código mais típico var
mais típico, não trabalho, no sentido de que ele imprimirá i: 6
cinco vezes. Mas esse código funciona:
for (let i=1; i<=5; i++) { setTimeout(function(){ console.log("i:",i); },i*1000); }
Ele imprimirá o seguinte i: 1
, i: 2
, i: 3
, etc. Por quê?
Porque a especificação ES6 realmente diz que let i
em um for
escopos de cabeçalho de loop i
não apenas para o for
loop, mas para o cada iteração do for
loop. Em outras palavras, ele faz com que se comporte assim:
{ let k; for (k=1; k<=5; k++) { let i = k; // <-- new `i` for each iteration! setTimeout(function(){ console.log("i:",i); },i*1000); } }
Isso é muito legal – resolve um problema muito comum que os desenvolvedores têm com closures e loops!
Nota: Isso ainda não funciona nos navegadores, mesmo naqueles com let
. A especificação ES6 exige isso, mas, no momento em que este artigo foi escrito, nenhum navegador estava em conformidade com essa nuance específica por iteração. Se o senhor quiser uma prova, tente colocar o código em ES6fiddle. Veja…
Ainda mais explícito let
Escopo
OK, então talvez eu tenha convencido o senhor de que explícito são um pouco melhores. A desvantagem do que foi dito acima é que não é obrigatório que o senhor siga esse estilo/idioma de { let b, c; .. }
o que significa que o senhor ou outra pessoa da sua equipe pode cometer um erro e não segui-lo.
Há outra opção. Em vez de usar o “let
formulário de declaração”, poderíamos usar o “let
block form”:
if (a) { // make an explicit scope block! let (b, c) { // .. } }
É uma pequena mudança, mas o senhor deve prestar atenção: let (b, c) { .. }
cria um bloco explícito de escopo para o b
e c
. É sintaticamente necessário que o b
e c
a ser declarado no topo, e é um bloco que nada mais é do que um escopo.
Na minha opinião, essa é a melhor maneira de usar o let
-com base no escopo do bloco.
Mas há um problema. O comitê TC39 votou a favor de não incluir esta forma particular de let
no ES6. Ela pode aparecer mais tarde, ou nunca, mas definitivamente não está no ES6.
Ugh. Mas esse não é o primeiro, nem o último, caso em que algo que é mais preferível perde para uma opção inferior.
Então, será que estamos presos à forma anterior?
Talvez não. Criei uma ferramenta chamada “let-er”, que é um transpilador para “let
block form”. Por padrão, ele está no modo somente ES6 e aceita códigos como:
let (b, c) { .. }
E produz:
{ let b, c; .. }
Isso não é muito ruim, não é? Na verdade, é uma transformação bastante simples para obter “let
block form” para o padrão “let
declaration form” padrão. Depois que o senhor executar o let-er para essa transformação, o senhor pode usar um transpilador ES6 comum para atingir ambientes pré-ES6 (navegadores, etc.).
Se o senhor quiser usar o let-er autônomo sem nenhum outro transpilador, apenas para let
-com base em escopo de bloco, o senhor pode definir opcionalmente o modo/flag ES3 e, em vez disso, ele produzirá isso (um lixo reconhecidamente hacker):
try{throw void 0}catch( b ){try{throw void 0}catch( c ){ .. }}
Sim, ele usa o fato pouco conhecido de que try..catch
tem o escopo de bloco incorporado no catch
.
Ninguém quer escrever esse código, e ninguém gosta do desempenho degradado que ele traz. Mas lembre-se de que se trata de código compilado e que serve apenas para direcionar navegadores muito antigos, como o IE6. O desempenho mais lento é lamentável (cerca de 10% em meus testes), mas seu código já está sendo executado de forma muito lenta/ruim no IE6, portanto…
Enfim, let-er por padrão, tem como alvo o ES6 padrão e, portanto, funciona bem com outras ferramentas ES6, como transpiladores padrão.
A escolha a ser feita é se o senhor prefere criar código com let (b, c) { .. }
ou o senhor prefere criar código com o estilo { let b, c; .. }
OK o suficiente?
Eu uso let-er em meus projetos agora. Acho que é a melhor maneira. E espero que, talvez no ES7, os membros do TC39 percebam a importância de adicionar o “let
block form” ao JS, para que eventualmente o let-er pode ir embora!
De qualquer forma, explícito o escopo do bloco é melhor do que implícito. Por favor, bloqueie o escopo de forma responsável.
let
Substitui var
?
Alguns membros proeminentes da comunidade JS e do comitê TC39 gostam de dizer: “let
é o novo var
.” Na verdade, alguns sugeriram literalmente (espero que em tom de brincadeira!?) fazer apenas um find-n-replace global de var
para let
.
Não consigo expressar o quanto esse conselho seria incrivelmente estúpido.
Em primeiro lugar, os riscos que mencionamos acima teriam uma probabilidade muito maior de aparecer em seu código, pois é provável que seu código não esteja perfeitamente escrito com relação a var
utilização. Por exemplo, esse tipo de código é extremamente comum:
if ( .. ) { var foo = 42; } else { var foo = "Hello World"; }
Todos nós provavelmente concordamos que o deve ter sido escrito como:
var foo; if ( .. ) { foo = 42; } else { foo = "Hello World"; }
Mas ainda não está escrito dessa forma. Ou, acidentalmente, o senhor está fazendo coisas como:
b = 1; // .. var b;
Ou o senhor está contando acidentalmente com o fechamento sem escopo de bloco em loops:
for (var i=0; i<10; i++) { if (i == 2) { setTimeout(function(){ if (i == 10) { console.log("Loop finished"); } },100); } }
Portanto, se o senhor simplesmente substituir cegamente o var
por let
no código existente, há uma boa chance de que pelo menos algum lugar pare de funcionar acidentalmente. Todos os itens acima falhariam se o let
substituído var
, sem outras alterações.
Se o senhor for readaptar o código existente com o escopo de bloco, precisará analisar caso a caso, com cuidado, e raciocinar se o escopo de bloco é apropriado ou não.
Certamente haverá lugares onde um var
era usado estilisticamente, e agora um let
é melhor. Tudo bem. Continuo não gostando do implícita mas se essa for a sua praia, que seja.
Mas também haverá lugares em que, em sua análise, o senhor perceberá que o código tem problemas estruturais, onde um let
seria mais estranho ou criaria um código mais confuso. Nesses locais, o senhor pode optar por corrigir o código, mas também pode decidir, de forma bastante razoável, deixar o var
sozinho.
Aqui está o que mais me incomoda no “let
é o novo var
“: ele pressupõe, quer o senhor admita ou não, uma visão elitista de que todo código JS deve ser perfeito e seguir as regras adequadas. Sempre que o senhor mencionar esses casos anteriores, os proponentes simplesmente responderão: “bem, esse código já estava errado”.
Claro, mas esse é um ponto secundário, não o ponto principal. É igualmente hostil dizer: “use apenas let
se o seu escopo já estiver perfeito ou se o senhor estiver preparado para reescrevê-lo para torná-lo perfeito e mantê-lo perfeito”.
Outros proponentes tentarão amenizar isso com “bem, basta usar o let
para todos os novos códigos”.
Isso é igualmente elitista, porque, mais uma vez, pressupõe que, uma vez que o senhor aprende let
e decidir usá-lo, espera-se que o senhor escreva todo o código novo sem nunca se deparar com padrões perigosos.
Aposto que os membros do TC39 podem fazer isso. Eles são muito inteligentes e têm muita intimidade com o JS. Mas o restante de nós não tem tanta sorte.
let
é o novo companheiro do var
A perspectiva mais razoável e mais realista, a que eu adoto porque minha principal interface com JS é por meio dos alunos/participantes com quem falo, ensino e trabalho, é adotar a refatoração e a melhoria do código como um processo, não como um evento.
É claro que, ao aprender boas práticas recomendadas de escopo, o código deverá ficar um pouco melhor a cada vez que for tocado, e é claro que o novo código deverá ser um pouco melhor do que o antigo. Mas o senhor não liga um interruptor simplesmente lendo um livro ou uma publicação de blog e, de repente, tem tudo perfeito.
Em vez disso, acho que o senhor deve abraçar tanto o novo let
e o antigo var
como sinais úteis em seu código.
Use let
em locais onde o senhor sabe que precisa de escopo de bloco e já pensou especificamente nessas implicações. Mas continue a usar var
para variáveis que não podem ter escopo de bloco facilmente ou que não devem ter escopo de bloco. Haverá lugares no código do mundo real em que algumas variáveis terão o escopo adequado para toda a função, e para essas variáveis, var
é um sinal melhor.
function foo() { var a = 10; if (a > 2) { let b = a * 3; console.log(b); } if (a > 5) { let c = a / 2; console.log(c); } console.log(a); }
Nesse código, let
grita para mim: “ei, estou com escopo de bloco!”. Isso chama minha atenção e, portanto, dou mais atenção a ele. O var
apenas diz: “ei, sou a mesma variável de sempre com escopo de função, porque vou ser usada em vários escopos”.
Que tal dizer apenas let a = 10
no nível superior da função? O senhor pode fazer isso, e o senhor vai conseguir.
Mas não acho que seja uma boa ideia. Por quê?
Primeiro, o senhor perde/degrada a diferença de sinal entre var
e let
. Agora, é apenas a posição que sinaliza a diferença, e não a sintaxe.
Em segundo lugar, ainda é um risco em potencial. Já teve um bug estranho em um programa e começou a lançar try..catch
para tentar resolver o problema? Eu com certeza.
Ops:
function foo() { try { let a = 10; if (a > 2) { let b = a * 3; console.log(b); } } catch (err) { // .. } if (a > 5) { let c = a / 2; console.log(c); } console.log(a); }
O escopo de bloco é ótimo, mas não é uma solução milagrosa e não é apropriado para tudo. Há lugares em que o escopo da função de var
s e, de fato, do comportamento “hoisting”, são bastante úteis. Essas não são falhas abjetas na linguagem que devem ser removidas. São coisas que devem ser usadas com responsabilidade, assim como o let
.
Aqui está a melhor maneira de dizer isso: “let
é o novo escopo de blocos var
“. Essa declaração enfatiza que let
deve substituir o var
somente quando var
já estava sinalizando estilisticamente o escopo do bloco. Caso contrário, deixe o var
sem problemas. Ele ainda está fazendo seu trabalho muito bem!
Resumo
O escopo do bloco é legal, e o let
nos dá isso. Mas seja explícito sobre seus escopos de bloco. Evite implícitos let
espalhadas por aí.
let
+ var
, não s/var/let/
. Apenas franza a testa e depois sorria para a próxima pessoa que lhe disser: “let
é o novo var
.”
let
melhora as opções de escopo em JS, não substitui. var
ainda é um sinal útil para variáveis que são usadas em toda a função. Ter ambos e usar ambos significa que a intenção de escopo é mais clara de entender e manter e aplicar. Essa é uma grande vitória!
Sobre Kyle Simpson
Kyle Simpson é um engenheiro de software orientado para a Web, amplamente aclamado por sua série de livros “You Don’t Know JS” e por quase 1 milhão de horas vistas de seus cursos on-line. O superpoder de Kyle é fazer perguntas melhores, e ele acredita profundamente no uso máximo das ferramentas minimamente necessárias para qualquer tarefa. Como um “tecnólogo centrado no ser humano”, ele é apaixonado por unir humanos e tecnologia, desenvolvendo organizações de engenharia para resolver os problemas certos, de maneiras mais simples. Kyle sempre lutará pelas pessoas por trás dos pixels.