Explorando o escopo no Javascript
Escopo é o que determina o acesso e visibilidade de variáveis no Javascript
Vejamos um exemplo simples.
// scope a
let a = 1
{
// scope b
let a = 2
console.log(a) //2
}
console.log(a) //1
Nesse código temos duas variáveis com o mesmo nome a
, isso só é possível porque as variáveis foram
declaras em escopos diferentes. Ao longo desse post vamos explorar mais sobre scopo no javascript.
1° Regra: Escopos horizontais
Escopos horizontais são escopos que não possuem hierarquia.
Escopos horizontais não compartilham declarações de variáveis.
{
//scope a (sem hierarquia)
}
{
//scope b (escopo externo)
{
//scope b-1 (escopo interno)
}
}
Vejamos o próximo exemplo:
{
// scope a
let x
}
{
// scope b
console.log(x)
}
Executando esse 👆 código vamos ter o seguinte error.
🚨 ReferenceError: x is not defined
A variável x
está apenas disponível para o escopo a
.
2° Regra: Escopos verticais
Para escopos verticais temos um comportamento diferente do anterior.
Tudo que for declarado no escopo pai será visível no escopo filho, o contrário não é verdadeiro
Exemplo:
{
//scope a(pai)
let x = 'hello world!'
{
//scope b(filho)
console.log(x)
}
}
👆 Esse o código funciona perfeitamente.
{
//scope a(pai)
{
//scope b(filho)
let x = 'hello world'
}
console.log(x)
}
Já neste aqui 👆 teremos o seguinte erro.
🚨 ReferenceError: x is not defined
Logo, tudo que é declarado no escopo pai pode ser acessado no escopo filho, e tudo que é declaro no escopo filho não pode ser acessado no escopo pai.
Elementos que definem escopos
- function
- if/else
- for(in/off)
- while/do-while
A primeira e a segunda regra se aplicam a todos os elementos que definem escopo.
Exemplo: funções
function fn1() {
let x = 'hello world'
console.log('fn1:', x)
return function fn2() {
console.log('fn2:', x)
return function fn3() {
console.log('fn3:', x)
}
}
}
fn1()()()
Executando esse exemplo teremos a seguinte saída:
fn1: hello world
fn2: hello world
fn3: hello world
A variável x
no escopo pai(fn1
) foi acessada pelos escopos filhos(fn2, fn3
).
Quando movemos a variável(x
) para um dos escopos filhos.
function fn1() {
console.log('fn1:', x)
return function fn2() {
console.log('fn2:', x)
return function fn3() {
let x = 'hello world'
console.log('fn3:', x)
}
}
}
fn1()()()
console.log('fn1:', x)
^
🚨 ReferenceError: x is not defined
Teremos um error por não conseguir acessar a variável x
.
O exemplo anterior também server para outros elementos que também definem escopo, o comportamento será o mesmo.
Usando if
para o mesmo exemplo.
if (true) {
let x = 'hello world'
console.log('if1', x)
if (true) {
console.log('if2', x)
if (true) {
console.log('if3', x)
}
}
}
👆 Funciona normalmente. 👇
fn1: hello world
fn2: hello world
fn3: hello world
Movendo a variável x
para um dos escopos filhos teremos o mesmo erro que ocorreu no exemplo com funções.
if (true) {
console.log('if1', x)
if (true) {
console.log('if2', x)
if (true) {
let x = 'hello world'
console.log('if3', x)
}
}
}
console.log('if1:', x)
^
🚨 ReferenceError: x is not defined
Hoisting
if (true) {
console.log('if1', x)
if (true) {
console.log('if2', x)
if (true) {
var x = 'hello world'
console.log('if3', x)
}
}
}
Nos exemplos anteriores usamos o let
para declarar nossas variáveis.
Quando substituímos o let
pelo var
o nosso código vai imprimir a seguinte saída ao
invés de retornar um erro.
if1 undefined
if2 undefined
if3 hello world
Isso ocorre no Javascript por conta de um comportamento chamado de hoisting.
Hoisting é um comportamento do Javascript que permite que variáveis sejam usadas antes de serem declaradas
Variáveis declaradas com let
e const
são elevadas ao topo, mas não são inicializadas.
console.log(x)
const x = 'hello world'
👇 Executando.
🚨 ReferenceError: Cannot access 'x' before initialization
Variáveis declaradas com var
também são elevadas ao topo, mas são inicializadas com o valor indefinido undefined
.
console.log(x)
var x = 'hello world'
👇 Executando.
undefined
O Javascript apenas eleva as declarações, não atribuições.
console.log(x)
var x // declaração elevada ao topo
x = 'hello world' // inicialização não elevada
consol.log(x)
👇 Executando.
undefined
hello world
Hoisting e funções
Agora vamos observar o hoisting aplicado em funções.
console.log(fn())
function fn() {
// função elevada ao topo
return 'hello world!'
}
hello world
Neste exemplo a nossa função é chamada antes de ser declarada, o código funciona sem erros.
Agora vamos fazer uma pequena alteração.
console.log(fn())
var fn = function () {
return 'hello world!'
}
Executando esse código recebemos o seguinte erro:
🚨 TypeError: fn is not a function
Esse erro ocorre porque a variável(fn
) é declada, elevada, e na sua atribuição inicial ela
recebe o valor undefined
, ou seja, não pode ser chamada como função, por esse
motivo na primeira tentativa de chamada receberemos o erro: TypeError: fn is not a function
.