Saspi 4

Ult Combo's Front-end/JS mini-course

Escopo

Escopo


Variáveis e funções são acessíveis somente dentro de um determinado escopo.

Keyword var


Declara variável(is) dentro de um escopo.

Escopo Léxico


JavaScript é lexicamente escopado.

Funções aninhadas geram escopos aninhados.

Um escopo pode acessar todas variáveis e funções definidas em escopos pais.

Escopo Léxico


var global = 10;
(function() {
    var a = 1;
    (function() {
        var b = 2;
        (function() {
            var c = 3;
            console.log(global + a + b + c); //16
        }());
    }());
    console.log(b); //erro
}());

Escopo Léxico


(function() {
    var x = 2;
    (function() {
        x = 5;
    }());
    console.log(x); //5
}());

(function() {
    var x = 2;
    function setX(novoX) {
        x = novoX;
    }
    setX(5);
    console.log(x); //5
}());

Tipos de escopo


  • Escopo global (window)
  • Escopo de função
  • Escopo do mau de eval


Nota: O bloco catch é único no sentido de que este cria um novo LexicalEnvironment derivado do atual e anexa a Exception ao LexicalEnvironment criado, enquanto mantem o VariableEnvironment inalterado.

O bloco with também cria um LexicalEnvironment aumentado a partir do atual, porém este geralmente é evitado devido ao seu fácil mau uso.

Escopo global


JavaScript, executado no ambiente de um browser, possui um escopo global implícito, o qual pode ser acessado explicitamente através do objeto window.

window.alert === alert; //true

Escopo global


No modo não-estrito, atribuir um valor a uma variável não declarada gera uma variável global implicitamente.

(function() {
    oi = 1;
}());
console.log(oi); //1
console.log(window.oi);//1

No entanto, utilizar variáveis não declaradas é uma péssima prática pois prejudica a legibilidade do código.

Escopo global


No modo estrito do ES5, atribuir um valor a uma variável não declarada resulta em um erro.
(function() {
    "use strict";
    oi = 1; //erro
}());
Se você precisa declarar variáveis globais a partir de um escopo não-global, faça-o explicitamente:
(function() {
    "use strict";
    window.oi = 1;
}());
console.log(oi); //1
console.log(window.oi); //1

Escopo global


Todas declarações de funções e variáveis colocadas no escopo global tornam-se propriedades do objeto window  implicitamente.

function fun() {
    var naoGlobal = 'me acesse se puder';
}
var numero = 7;

console.log(window.fun); //function fun() {}
console.log(window.numero); //7
console.log(window.naoGlobal); //undefined

Escopo de função


Sempre que uma função é chamada, uma série de operações é executada antes de seu código ser então executado.

Primeiramente precisamos conhecer alguns termos do ECMAScript para entender melhor o que ocorre.

Execution Context


Inicialmente, é criado um contexto de execução global para cada página. Toda vez que entramos em uma função (e eval) é criado um novo contexto de execução.

Contextos de execução são empilhados, ou seja, o contexto ativo sempre está no topo da pilha e o global na última posição da pilha.

Execution Context


Cada contexto de execução possui três componentes:

  • LexicalEnvironment: Referencia o Lexical Environment usado para determinar referências de identificadores dentro do contexto de execução.
  • VariableEnvironment: Referencia o Lexical Environment cujo Environment Record mantém vinculações das declarações de variáveis e funções criadas dentro do contexto de execução.
  • ThisBinding: O valor associado à keyword this dentro do contexto de execução.

Lexical Environments


É, basicamente, um objeto (interno da linguagem) que mapeia identificadores à variáveis e funções baseado na estrutura de aninhamento lexical do código ECMAScript.

Este tipo de objeto possui duas propriedades:
  • Um objeto Environment Record
  • Referência a um Lexical Environment exterior
    (possivelmente nula)

Environment Record

Declarative Environment Record

É um objeto que mapeia declarações de variáveis e/ou funções de um determinado escopo a seus respectivos valores.
Por exemplo, este pode mapear declarações de funções, variáveis e argumentos do escopo de uma determinada função.

Object Environment Record

É um objeto que mapeia propriedades de um determinado objeto a seus respectivos valores. Exemplo: objeto global.

Entrando no código de função

  1. Cria-se um novo Contexto de Execução.
  2. Atribui-se thisArg à propriedade ThisBinding do contexto. (assunto para outra aula)
  3. Cria-se um Lexical Environment, com a propriedade Lexical Environment exterior definida para o Lexical Environment exterior da função em questão, e a propriedade Environment Record para uma nova Declarative Environment Record vazia.
  4. Atribui-se o Lexical Environment criado a ambas as propriedades LexicalEnvironment e VariableEnvironment do contexto.

Simplificação


O Environment Record guarda as declarações de funções e variáveis de um escopo, o qual é parte de um Lexical Environment ao lado de uma ligação com o escopo pai, que é parte de um contexto de execução.

lexEnv = {
    envRecord: {
        //identificador: posição na memória da variável/função
    },
    lexEnvPai: funcPaiEnv //ou globalObjectEnv, evalEnv, null
};
contexto = {
    LexicalEnvironment: lexEnv,
    VariableEnvironment: lexEnv,
    ThisBinding: thisArg
};

Qual a diferença entre LexicalEnvironmentVariableEnvironment se ambos tem o mesmo valor?


Sim, inicialmente ambos tem o mesmo valor. Porém, alguns blocos como catch e with modificam/aumentam o mapeamento de identificadores (LexicalEnvironment) sem acrescentar nenhuma declaração (mantem o mesmo VariableEnvironment).

Logo, as duas propriedades são necessárias para obter o comportamento especificado destes blocos.

Entrando no código de função

  1. Executa a Declaration Binding Instantiation:
    1. Para cada parâmetro da função, cria-se uma propriedade no Environment Record com o nome do parâmetro e com o valor do n-ésimo argumento passado (ou undefined caso não tenha sido passado).
    2. Escaneia o corpo da função por Function Declarations. Para cada uma encontrada cria-se uma propriedade no Environment Record com o nome da função e cujo valor é uma referência à função. Caso o nome de função já exista, há sobrescrita.
    3. Escaneia o corpo da função por declarações de variáveis. Para cada uma encontrada, se já não houver uma propriedade de mesmo nome no Environment Record, cria uma propriedade com o identificador da variável e inicializa-a para undefined.

Exemplificação


function f(param) {
    function fd() {}
    var variavel = 1;
}
f();
//antes da execução de f:
fEnvRecord = {
    fd: function fd() {},
    variavel: undefined
};

Resumindo


Quando entramos em uma função F, as FDs de seu escopo são declaradas e definidas antes da execução de F, logo acessíveis em todo seu escopo.

As variáveis também são declaradas antes da execução de F, mas estas possuem valor undefined até que a execução de F atribua algum valor a elas.

Este comportamento é amplamente conhecido como hoisting.

Variable shadowing

var myname = "global";
function func() {
    console.log(myname); //undefined
    var myname = "local";
    console.log(myname); //"local"
    console.log(window.myname); //"global"
}
func();
Quando um identificador de um escopo local tem o mesmo nome de um identificador de um escopo exterior, diz-se que o identificador exterior foi shadowed.
No entanto, caso o escopo que desejarmos acessar seja o global, você pode referir-se a ele explicitamente através do objeto window.

Exercícios


Ult JS #3 - Escopo

By Fabrício Matté

Ult JS #3 - Escopo

  • 1,223