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
-
Cria-se um novo Contexto de Execução.
-
Atribui-se thisArg à propriedade ThisBinding do contexto. (assunto para outra aula)
-
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.
-
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 LexicalEnvironment e VariableEnvironment 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
-
Executa a Declaration Binding Instantiation:
-
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).
-
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.
-
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.