Escopo e Hoisting no Javascript
A forma como o javascript lida com o escopo de variáveis e funções pode ser um tanto quanto confusa para quem vem de outras linguagens imperativas como C, Java ou C++. Enquanto nessas linguagens as variáveis possuem um escopo de bloco, no javascript as variáveis possuem escopo de função. Vamos ver um exemplo em C:
Que valor você acha que a variável idade terá no momento de escrever o resultado na tela? Se respondeu 12, acertou. Isso acontece no C porque blocos como if, while e for possuem seu próprio escopo dentro da função. A variável idade dentro do if é diferente da variável idade da função main. Já como a variável ano foi criada apenas dentro do bloco if, ela não existe do lado de “fora”, e portanto, esse código causaria um erro.
Agora vejamos como esse mesmo código funcionaria no Javascript:
Mas como pode ter funcionado? O que acontece é que no javascript blocos como o if, for e while não possuem escopos com variáveis. Todas as variáveis são sempre colocadas dentro do escopo da função. Quando você declara a variável ano por exemplo, ela será válida na função também, pois é onde fica delimitado seu escopo. Com relação à variável idade, você estaria apenas mudando o seu valor.
Mas, como o javascript faz para a função enxergar uma variável dentro de um bloco? Isso é feito por um recurso da linguagem chamado hoisting. Hoisting em inglês quer dizer içar ou erguer com um guindaste, que tem a ver com o que o Javascript realmente faz.
Não importa o lugar onde declaramos uma função ou variável e nem em qual ordem, o Javascript sempre vai movê-la sorrateiramente através do interpretador pro lugar mais alto do seu escopo. Ou seja, nos dois exemplos abaixo, as duas funções são idênticas para o interpretador do Javascript:
O que o Javascript fez aqui foi mover todas as declarações de variáveis para o ponto mais alto do escopo. É por isso que podemos utilizar variáveis declaradas dentro de blocos no escopo da função. E o mesmo acontece com funções.
No Javascript podemos declarar funções de duas maneiras:
No caso de funções declaradas explicitamente, o Javascript irá movê-la inteira para o início do escopo mais próximo. Mas, quando a função é declarada dentro de uma variável, apenas a variável é movida, o que pode causar mais uma confusão. Veja o exemplo:
Aqui, a função imprimirAno foi movida inteira para o início do escopo, enquanto que no caso de imprimirNome, apenas a declaração da variável foi movida pra cima. Enquanto a declaração de todas as variáveis são movidas, suas definições só acontecem no momento que o interpretador chega no local de execução do código. O exemplo acima na verdade foi interpretado Javascript assim:
O que muda com let e const
Desde o ECMAScript 6, o javascript passou a ter duas novas maneiras de declarar variáveis. Vou explicar como cada uma delas altera a forma como o javascript trabalha com escopo e hoisting de variáveis e funções. Veja esse exemplo:
Que valor você acha que o x terá no momento de mostrar o resultado na tela? Se respondeu 1, acertou!
O que acontece aqui é que variáveis declaradas com o let terão um escopo de bloco, ou seja, a variável será içada (hoisting) até o início do primeiro bloco que encontrar. A partir de agora então, não será mais preciso utilizar a técnica IIFE para isolar uma variável do resto do código.
Nesse exemplo, apenas a variável a foi alterada quando saiu do bloco, pois com ela o hoisting foi feito com base no escopo de função, enquanto que o b dentro do if deixa de existir assim que o bloco é finalizado.
A próxima keyword inserida no Javascript é interessante pois com ela conseguimos bloquear uma variável de ser redeclarada com outro valor. Veja um exemplo:
Depois que definimos um valor pela primeira vez para uma variável declarada com o const, não poderemos redefinir esse valor novamente. Isso é útil pois impede que outras partes do programa utilize essa variável para armazenar outro valor. Note no entanto que, apesar dela bloquear a redefinição de valores, não impede que uma variável que referencia um objeto, tenha o valor de algum de seus atributos alterados. Veja que o exemplo abaixo é perfeitamente possível:
Para fazer com que um objeto seja completamente imutável, é necessário congelá-lo. Isso pode ser feito com Object.freeze()
:
Ainda assim, o Object.freeze()
congela apenas o primeiro nível de atributos. Caso exista uma variável que referencie a um objeto, esse objeto não será congelado pelo método. Caso se intesse em utilizar objetos imutáveis, veja a biblioteca Immutable.js. É muito interessante e vale o estudo.
Conhecendo a forma como a linguagem lida com o escopo e o conceito de hoisting, fica muito mais fácil entender problemas que até então pareceriam completamente sem sentido. No entanto, o Javascript também possui um recurso fundamental pra quem deseja se tornar um Jedi: as Closures.
Falarei sobre elas no próximo artigo. Até la!
Deixe um comentário