Aguarde...

17 de março de 2020

Javascript Scope Explicado

Javascript Scope Explicado

O escopo é um dos tópicos mais importantes do JavaScript. Neste tutorial, você aprenderá sobre o que é escopo. Em seguida, você aprenderá sobre os tipos de escopo e como eles funcionam. Você também aprenderá sobre variáveis ​​ES6, instruções de bloco, por que elas são importantes para o escopo. Por fim, você também aprenderá sobre fechamentos.

O básico do escopo

Então, o que é escopo? Na programação, o escopo refere-se à visibilidade e acessibilidade de variáveis, funções e objetos durante o tempo de execução. Simplificando, o escopo diz se você pode usar variáveis, funções ou objetos específicos no seu código em um local específico ou não. E o tempo de execução? Tempo de execução é um período durante o qual um programa de computador está sendo executado.

Isso é importante. Isso significa que nem todas as variáveis, funções e objetos estão sempre visíveis e acessíveis em todos os lugares. Graças ao escopo, variáveis, funções e objetos podem estar visíveis e acessíveis em qualquer lugar, ou não. Depende de qual escopo você os cria. Se eles não estiverem visíveis, o JavaScript não permitirá que você os use.

E se você tentar usar alguma variável, função ou objeto que não está acessível no seu escopo atual? O JavaScript informará que a variável, função ou objeto não está definido. Isso está correto porque essa coisa realmente não está definida. Não existe no seu escopo atual.

Mais uma coisa sobre o escopo. Você também pode criar escopo dentro de um escopo ou, digamos, escopos “filho”. Estes são chamados escopos lexicais. Nesse caso, esses escopos lexicais podem acessar variáveis, funções ou objetos definidos em um escopo pai. No entanto, o escopo pai não pode acessar variáveis, funções ou objetos definidos em seus escopos lexicais.

Não se preocupe se isso parecer muito difícil agora. Falaremos mais sobre tudo isso e mais adiante neste tutorial. Também haverá exemplos que ajudarão você a entender todos esses conceitos. Mas antes disso, vamos primeiro falar sobre alguns benefícios do escopo.

Benefícios do escopo

Por que o escopo e essa acessibilidade e visibilidade limitadas são uma coisa boa? Primeiro, ele torna seu código mais seguro. Imagine que você tem um sistema com diferentes tipos de usuários. Alguns desses usuários são administradores e outros são usuários. Digamos que você conceda a todos acesso total a todas as partes do sistema. E se algo ruim acontecer?

Por exemplo, e se alguém excluir alguns arquivos importantes, alterar alguns registros ou interromper o sistema? Como você vai descobrir quem fez isso? Isso pode ser quase impossível, dependendo do número de usuários do sistema. Como você pode impedir que isso aconteça? Você pode limitar o acesso que cada tipo de usuário possui.

Você pode dar acesso total aos administradores e acesso limitado aos usuários. Isso tornará menos provável a ocorrência de acidentes. E, se algo acontecer, você saberá quem é o responsável.

Segundo, a acessibilidade e a visibilidade limitadas tornam seu código mais seguro. Quando todo o seu código está visível e acessível em qualquer lugar, é fácil encontrar problemas. Por exemplo, você pode acidentalmente algum nome de variável ou função duas vezes. Nesse caso, a nova variável, ou função, reescreverá a mais antiga.

Geralmente, é menos provável que isso aconteça quando você limita a acessibilidade e a visibilidade. Com o escopo, você pode usar os mesmos nomes com segurança, sem precisar se preocupar com nada, desde que o escopo seja sempre diferente. Dito isto, usar os mesmos nomes não é uma prática que eu recomendaria seguir.

Terceiro, acessibilidade e visibilidade limitadas ajudam a usar a memória com mais eficiência. Imagine carregar todas as variáveis, funções, objetos etc. o tempo todo, não importa se você realmente os usa ou não. Isso levaria rapidamente ao alto uso de memória e menor desempenho. Nada disso é bom e definitivamente não é necessário.

Quarto, acessibilidade e visibilidade limitadas facilitam a depuração. É mais fácil e rápido rastrear e corrigir bugs quando você trabalha com pequenos pedaços de código, ou seja, vários escopos pequenos, do que quando você trabalha com um grande pedaço de código, ou seja, um escopo.

Escopo em JavaScript

Ok, isso foi sobre o “o quê” e o “porquê”. Agora, é hora de aprender sobre como o escopo realmente funciona. Em JavaScript, existem tipos de escopo, global e local.

Âmbito global

O primeiro tipo de escopo é um escopo global. Esse escopo é criado automaticamente. Tudo o que não é definido dentro de um escopo local é automaticamente em um escopo global. Se você executar seu código em um navegador da Web, o escopo global será um windowobjeto. No caso do Node.js, será global .

Quando algo é definido em um escopo global, significa que ele é acessível e visível de qualquer lugar no seu código.

// Global scope
// Variable declared in a global scope
var myVar = 'Global variable one.'
let myLet = 'Global variable two.'
const myConst = 'Global variable three.'

// Try to access global variable from a function
function readVariable() {
  // Return global variable myVar
  // myVar variable is accessible everywhere
  return myVar
}

// Call readVariable function
readVariable()
// 'Global variable one.'

// Log global variable myVar
console.log(myVar)
// 'Global variable one.'

Como discutimos no início, definir variáveis, funções ou objetos no escopo global não é uma boa prática, e deve ser evitada. Você deve sempre, ou quase sempre, usar o escopo local.

Escopo local

O segundo tipo de escopo é o escopo local. Variáveis, funções e objetos definidos em um escopo local são visíveis e acessíveis apenas dentro desse escopo. Eles não são visíveis e acessíveis fora dele. A exceção aqui são os escopos internos, ou “filhos”, sobre os quais falamos brevemente no início.

Diferentemente do escopo global, o escopo local não é criado automaticamente. Bem, quase. O JavaScript criará o escopo local automaticamente se você der um motivo. Quão? Criando uma função . Em JavaScript, toda função cria seu próprio escopo local. É também por isso que o escopo “local” às vezes é chamado de escopo “função”.

Quando você cria alguma função e define uma variável, objeto ou outra função dentro dela, a “coisa” que você definiu será definida em um escopo local. Será uma variável local, objeto ou função. Isso também significa que ele estará visível ou acessível apenas dentro da função ou escopo local em que você a definiu.

Quando você tenta usar essa variável local, objeto, função, o JavaScript lançará um erro sobre algo que não está sendo definido.

// Local scope no.1:
// Different functions, different local scopes

// Create function to create new local scope
function myFunctionOne() {
  // Local scope no.1
}

// Create another function to create another new local scope
function myFunctionTwo() {
  // Local scope no.2
}

// Create another function to create another new local scope
function myFunctionThree() {
  // Local scope no.3
}


// Local scope no.2:
// Try to access variables in different local scopes
function myFunctionOne() {
  // Local scope no.1
  const myConstOne = 'I am inside local scope of myFunctionOne.'

  // Try to access myConstTwo variable
  // declared in local scope of myFunctionTwo
  // This doesn't work
  console.log(myConstTwo)
  // ReferenceError: myConstTwo is not defined
}

// Create another function to create another new local scope
function myFunctionTwo() {
  // Local scope no.2
  const myConstTwo = 'I am inside local scope of myFunctionTwo.'

  // Try to access myConstOne variable
  // declared in local scope of myFunctionOne
  // This doesn't work
  console.log(myConstOne)
  // ReferenceError: myConstOne is not defined
}

Âmbito lexical

Funções são usadas em JavaScript para criar escopo local. Com base nessa idéia, se você deseja criar outro escopo local dentro da função existente, dentro do escopo local existente, tudo o que você precisa fazer é definir outra função dentro dessa função, certo? Sim. Isso criará um novo escopo que existe dentro de outro escopo local externo.

Esse tipo de escopo também é chamado de escopo lexical. Imagine que você tem um grupo aninhado de funções. As funções internas têm acesso a todas as variáveis, objetos e funções existentes no escopo pai ou nas funções pai. Por outro lado, as funções externas não têm acesso a variáveis, objetos e funções que existem dentro de suas funções filho.

Traduzidas na linguagem dos desenvolvedores, as funções filho estão lexicamente ligadas ao contexto de execução de seus pais.

// Lexical scope no.1
// Create function to create new local scope
function myParentFunction() {
  // Local scope no.1
  const myParentConst = 'I am a local variable.'

  // Try to access local variable myParentConst
  // This works
  console.log(myParentConst)
  // 'I am a local variable.'

  function myChildFunction() {
    // Local scope no.2
    const myChildConst = 'I am a local local variable.'

    // Try to access local variable myChildConst
    // This works
    console.log(myChildConst)
    // 'I am a local local variable.'

    // Try to access local variable myParentConst
    // from the inside of myChildFunction
    // i.e: Try to access content of parent's scope from its child
    // This works
    console.log(myParentConst)
    // 'I am a local variable.'
  }

  // Try to access local variable myChildConst
  // from the outside of myChildFunction
  // i.e: Try to cess content of child's scope from its parent
  // This doesn't work
  console.log(myChildConst)
  // ReferenceError: myChildConst is not defined
}

// Try to access local variable myParentConst
// from the outside of myParentFunction
// This doesn't work
console.log(myParentConst)
// ReferenceError: myParentConst is not defined

Vida útil de um escopo

Isso era sobre escopo global e local. Uma coisa boa a lembrar é quanto tempo dura cada escopo, quanto tempo existe. Felizmente, a resposta é fácil. No caso de um escopo global, o escopo permanece enquanto sua aplicação permanecer. Portanto, se você tiver algumas variáveis ​​globais, funções ou objetos, elas existirão até você interromper o aplicativo ou fechar o navegador.

No caso de um escopo local? Qualquer coisa definida em um escopo local dura enquanto sua função, que cria o escopo local de que estamos falando, é chamada e executada. Quando a execução termina, a função e todo o seu conteúdo são coletados como lixo . Não existe mais.

Instruções de bloco, ES6 e escopo

Em JavaScript, também há instruções de bloqueio. Alguns deles são if...elseswitchcondições e forwhiledo...whilefor...infor...of laços . Nada disso cria novo escopo. Bem, a menos que você crie uma nova função dentro deles. No entanto, isso não mudaria nada, porque significaria que estamos usando funções novamente.

De qualquer forma, sem criar função interna, não há novo escopo local criado dentro de instruções de bloco. Isso significa que, quando você declarar uma nova variável, função ou objeto dentro de uma instrução de bloco, ela não será uma variável, função ou objeto local. Ainda será variável global, função ou objeto.

Como essa variável, função ou objeto é global, você pode acessá-lo de qualquer lugar. Bem, a menos que a instrução de bloco esteja dentro de uma função. Nesse caso, qualquer coisa dentro da instrução de bloco será definida dentro de um escopo local dessa função, que também contenha essa instrução de bloco.

// Block statement no.1: No local scope
// Create if..else statement with a var variable
if (true) {
  var myVar = 'I was supposed to be local.'
}

// Try to access variable myVar
// from the outside of if...else statement
// This works
console.log(myVar)
// 'I was supposed to be local.'

Isso foi na era pré-ES6. As coisas mudaram após o lançamento da especificação ES6. O ES6 não fez alterações no escopo ou bloqueou as próprias instruções. O que ele fez foi introduzir dois novos tipos de variáveis, a saber, letconst. Há diferenças importantes entre varletconste você aprender sobre eles aqui .

Para este tutorial, o importante é isso … Enquanto a varnão respeitar o conteúdo das instruções de bloco como um novo escopo, letconstfazer. Isso significa que quando você declara letou constvariável dentro de uma instrução de bloco, ela estará acessível apenas dentro dessa instrução, e não fora dela.

// Create if..else statement with a variable
if (true) {
  var myVar = 'I am var.'
  let myLet = 'I am let.'
  const myConst = 'I am const.'
}

// Try to log the var variable
// declared inside if...else statement
// This works
console.log(myVar)
// 'I am var.'


// Try to log the let variable
// declared inside if...else statement
// This doesn't work
console.log(myLet)
// ReferenceError: myLet is not defined

// Try to log the const variable
// declared inside if...else statement
// This doesn't work
console.log(myConst)
// ReferenceError: myConst is not defined

Esse também é um motivo pelo qual muitos desenvolvedores de JavaScript preferem usar letconstnão var. Ambos, letconst, oferecer maior grau de controle por causa de seu comportamento quando eles são usados em declarações bloco. É uma boa idéia começar a usar letconstabandonar lenta ou rapidamente var.

Encerramentos – Uma breve introdução

Em JavaScript, funções não são apenas funções. Eles também são fechamentos. Isso significa que as funções podem acessar variáveis ​​e também argumentos definidos fora deles e trabalhar com eles. Não apenas isso. Não apenas as funções têm acesso a variáveis ​​externas, elas também sempre acessam os valores mais recentes dessas variáveis.

Quando você cria uma função, e essa função contém outra função, essa função interna é um fechamento. Esse fechamento, a função interna, geralmente é retornado para que você possa usar as variáveis ​​da função externa posteriormente. Você verá isso em ação nos exemplos abaixo.

Fechamentos muito simples

Imagine que você tem uma função que acessa alguma variável do escopo externo. Agora, digamos que você chame essa função, altere a variável e chame essa função novamente. Essa função lerá o novo valor dessa variável, não o antigo. Isto é importante para anotar.

Isso significa que a função não está apenas copiando o valor dessa variável e armazenando-a em algum lugar para uso posterior. Em vez disso, ele está realmente acessando a variável específica, no momento da execução.

// Closure no.1
// Variable declared in a global scope
let name = 'Emmett Brown'

// Simple closure - function accessing outside variable
function introduceMe() {
  return `Hello, I am ${name}.`
}

// Call introduceMe function
introduceMe()
// 'Hello, I am Emmett Brown.'


// Test if introduceMe function
// has really access to "name" variable
// i.e. if it can read its current value
// Change the value of "name" variable
name = 'Marty McFly'

// Call introduceMe function again
introduceMe()
// 'Hello, I am Marty McFly.'

Encerramentos mais complexos pt.1

Na maioria dos casos, os fechamentos são mais complexos que o exemplo acima. Esses exemplos geralmente envolvem funções retornando funções que retornam algo. Nesse caso, o interessante é que a função interna retornada também pode acessar qualquer coisa passada para a função externa, pai, como argumento, juntamente com quaisquer variáveis ​​externas.

Em outras palavras, a função interna realmente lembra o que foi passado na função pai. Isso é verdade mesmo se a função interna for realmente executada muito mais tarde.

// Closure no.2: function returning a function
// Create outer function that accepts one parameter
function outerFunction(outerParam) {
  // Create inner function that also accepts one parameter
  return function innerFunction(innerParam) {
    // Log the value passed as a parameter
    // to the outer, parent, function
    console.log(outerParam)

    // Log the value passed as a parameter
    // to the inner function
    console.log(innerParam)
  }
}

// Try to call outerFunction right away
outerFunction('This is the outer parameter.')
// ... Nothing

// Assign the "outerFunction" to a variable
// Pass something as a argument
// this is the "outerParam"
const myFunction = outerFunction('This is the outer parameter.')

// Call the "myFunction"
// Pass something as a argument
// this is the "innerParam"
myFunction('This is the inner parameter.')
// 'This is the outer parameter.'
// 'This is the inner parameter.'

Fechamentos mais complexos pt.2

Outro caso de uso popular é quando a função externa contém alguma variável e a função interna retorna essa variável. A propósito, este é outro exemplo de escopo lexical, ou seja, funções internas podem acessar variáveis ​​definidas dentro de seu escopo pai.

// Closure no.3
// Create outer function
function collectStuff() {
  // Declare a local variable
  const stuff = ['paper', 'clips', 'pen', 'notebook']

  // Create, and return, inner function
  return function showStuff() {
    // Return the value of "stuff" variable
    // declared in parent scope
    return stuff
  }
}

// Try to call the "collectStuff" function right away
collectStuff()
// ... Nothing

// Assign the "collectStuff" to a variable
const myCollection = collectStuff()

// Call the function assigned to "myCollection"
myCollection()
// [ 'paper', 'clips', 'pen', 'notebook' ]

Explicação de exemplos em fechamentos mais complexos

Por que tentar chamar a função outerFunction()collectStuff()imediatamente não funcionou? Por que era necessário primeiro atribuir essas funções a uma variável e depois chamá-las? A resposta é simples. Nos exemplos acima, não chamamos essas funções internas. Nós apenas os devolvemos.

Então, quando chamamos as funções externas, elas simplesmente retornam, mas não chamam, as funções internas. Sim, essas funções internas foram criadas, mas nunca foram chamadas. Quando atribuímos as funções externas a uma variável, também as invocamos, as funções externas. Quando isso aconteceu, essas funções retornaram as funções internas.

O resultado foi que essas variáveis ​​realmente continham referência às funções internas retornadas e não à externa. Então, quando chamamos as variáveis, na verdade, e finalmente, chamamos as funções internas. Isso se aplica aos dois exemplos, com outerFunction()e com collectStuff(). Vamos dar uma olhada em como isso fica no código e também adicionar alguns logs.

// Create outer function
function collectStuff() {
  // Log a message when "collectStuff" function runs
  console.log('The "collectStuff" function is running!')

  // Declare a local variable
  const stuff = ['paper', 'clips', 'pen', 'notebook']

  // Create, and return, inner function
  return function showStuff() {
    // Log a message when "showStuff" function runs
    console.log('The "showStuff" function is running!')

    // Return the value of "stuff" variable
    // declared in parent scope
    return stuff
  }
}

// Try to call the "collectStuff" function right away
// This will call the "collectStuff" function
// that will return the "showStuff" function,
// but it will not call the "showStuff" function
// therefore the "showStuff" function will NOT run
collectStuff()
// 'The "collectStuff" function is running!'


// Assign the "collectStuff" to a variable
// This will also call the "collectStuff" function
// that will return the "showStuff" function
// reference to which will then be stored in "myCollection" variable
const myCollection = collectStuff()
// 'The "collectStuff" function is running!'
// Now, "myCollection" contains reference to "showStuff" function

// Call the function assigned to "myCollection"
// This will actually call the "showStuff" function
// because "myCollection" contains reference to "showStuff" function
myCollection()
// 'The "showStuff" function is running!'
// [ 'paper', 'clips', 'pen', 'notebook' ]

Vejo? O importante sobre o retorno de uma função interna de uma função é que a função retornada não será chamada automaticamente quando você tentar chamar a função externa. É por isso que você deve primeiro atribuir a função externa a uma variável e depois chamar a variável como uma função. Somente então a função interna será executada.

Chamando a função retornada sem atribuição

Existe uma maneira de chamar a função retornada sem atribuí-la a uma variável. Isso pode ser feito usando parênteses duas vezes ()(), no momento em que você chama a função externa. Isso chamará automaticamente a função interna também.

// Create outer function
function outerFunction() {
  // Log a message when "outerFunction" function runs
  console.log('The "outerFunction" function is running!')

  // Create, and return, inner function
  return function innerFunction() {
    // Log a message when "innerFunction" function runs
    console.log('The "innerFunction" function is running!')
  }
}

// Call the "outerFunction" function right away
// using parenthesis two times '()()'
outerFunction()()
// 'The "outerFunction" function is running!'
// 'The "innerFunction" function is running!'

Conclusão: escopo do JavaScript explicado

É isso aí. Você acabou de concluir este tutorial sobre o escopo do JavaScript. Hoje você aprende muitas coisas. Você aprendeu o básico do escopo e seus benefícios. Em seguida, você aprendeu sobre dois tipos de escopo, global e local, e como eles funcionam. Depois disso, você também aprendeu o escopo lexical e a vida útil de um escopo.

Depois de escopo, você também aprendeu sobre como varletconsttrabalho dentro de instruções de bloco. Como última coisa, você aprendeu sobre fechamentos e como eles funcionam. Espero que você tenha gostado deste tutorial.

Postado em Blog
Escreva um comentário