Explorando o escopo no Javascript

Carlos Costa

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.

Referências