O que é uma breve introdução ao escopo lexical?
O que é uma breve introdução ao escopo lexical?
Respostas:
Eu os entendo através de exemplos. :)
Primeiro, escopo lexical (também chamado de escopo estático ), na sintaxe do tipo C:
void fun()
{
int x = 5;
void fun2()
{
printf("%d", x);
}
}
Todo nível interno pode acessar seus níveis externos.
Existe uma outra maneira, chamada de escopo dinâmico usado pela primeira implementação do Lisp , novamente em uma sintaxe do tipo C:
void fun()
{
printf("%d", x);
}
void dummy1()
{
int x = 5;
fun();
}
void dummy2()
{
int x = 10;
fun();
}
Aqui você fun
pode acessar x
em dummy1
ou dummy2
, ou qualquer x
em qualquer função que chamar fun
com x
declarada nele.
dummy1();
imprimirá 5,
dummy2();
imprimirá 10.
O primeiro é chamado estático porque pode ser deduzido em tempo de compilação, e o segundo é chamado dinâmico porque o escopo externo é dinâmico e depende da chamada em cadeia das funções.
Acho o escopo estático mais fácil para os olhos. A maioria dos idiomas acabou por esse caminho, até o Lisp (pode fazer as duas coisas, certo?). O escopo dinâmico é como passar referências de todas as variáveis para a função chamada.
Como um exemplo de por que o compilador não pode deduzir o escopo dinâmico externo de uma função, considere nosso último exemplo. Se escrevermos algo como isto:
if(/* some condition */)
dummy1();
else
dummy2();
A cadeia de chamadas depende de uma condição de tempo de execução. Se for verdade, a cadeia de chamadas se parece com:
dummy1 --> fun()
Se a condição for falsa:
dummy2 --> fun()
O escopo externo de fun
ambos os casos é o chamador mais o chamador do chamador e assim por diante .
Apenas para mencionar que a linguagem C não permite funções aninhadas nem escopo dinâmico.
JavaScript
. Portanto, acho que isso não deve ser marcado como a resposta aceita. Âmbito lexical especificamente em JS é diferente
for
loop é o problema típico. O escopo lexical do JavaScript é apenas no nível da função, a menos que o ES6 let
ou const
seja usado.
Vamos tentar a definição mais curta possível:
O escopo léxico define como os nomes de variáveis são resolvidos em funções aninhadas: as funções internas contêm o escopo das funções pai, mesmo que a função pai tenha retornado .
Isso é tudo o que existe!
var scope = "I am global";
function whatismyscope(){
var scope = "I am just a local";
function func() {return scope;}
return func;
}
whatismyscope()()
O código acima retornará "Eu sou apenas um local". Não retornará "Eu sou global". Como a função func () conta onde está originalmente definido, está no escopo da função whatismyscope.
Ele não se incomoda com o que está sendo chamado (o escopo global / mesmo dentro de outra função), é por isso que o valor do escopo global Eu sou global não será impresso.
Isso é chamado de escopo lexical, em que " funções são executadas usando a cadeia de escopo que estava em vigor quando foram definidas " - de acordo com o JavaScript Definition Guide.
O escopo léxico é um conceito muito, muito poderoso.
Espero que isto ajude..:)
O escopo léxico (AKA estático) refere-se à determinação do escopo de uma variável com base exclusivamente em sua posição no corpus de código textual. Uma variável sempre se refere ao seu ambiente de nível superior. É bom entendê-lo em relação ao escopo dinâmico.
O escopo define a área em que funções, variáveis e outras estão disponíveis. A disponibilidade de uma variável, por exemplo, é definida dentro do seu contexto, digamos que a função, arquivo ou objeto em que estão definidos. Geralmente chamamos essas variáveis locais.
A parte lexical significa que você pode derivar o escopo da leitura do código fonte.
O escopo léxico também é conhecido como escopo estático.
O escopo dinâmico define variáveis globais que podem ser chamadas ou referenciadas de qualquer lugar após serem definidas. Às vezes, eles são chamados de variáveis globais, embora as variáveis globais na maioria das linguagens de programação sejam de escopo lexical. Isso significa que pode ser derivado da leitura do código que a variável está disponível neste contexto. Talvez seja necessário seguir uma cláusula de usos ou inclusões para encontrar a instância ou definição, mas o código / compilador conhece a variável nesse local.
No escopo dinâmico, por outro lado, você pesquisa primeiro a função local, depois a função que chamou a função local, depois a função que chamou essa função e assim por diante, na pilha de chamadas. "Dinâmico" refere-se à mudança, pois a pilha de chamadas pode ser diferente toda vez que uma determinada função é chamada e, portanto, a função pode atingir variáveis diferentes, dependendo de onde é chamada. (veja aqui )
Para ver um exemplo interessante de escopo dinâmico, consulte aqui .
Para mais detalhes, veja aqui e aqui .
Alguns exemplos em Delphi / Object Pascal
Delphi tem escopo lexical.
unit Main;
uses aUnit; // makes available all variables in interface section of aUnit
interface
var aGlobal: string; // global in the scope of all units that use Main;
type
TmyClass = class
strict private aPrivateVar: Integer; // only known by objects of this class type
// lexical: within class definition,
// reserved word private
public aPublicVar: double; // known to everyboday that has access to a
// object of this class type
end;
implementation
var aLocalGlobal: string; // known to all functions following
// the definition in this unit
end.
O Delphi mais próximo do escopo dinâmico é o par de funções RegisterClass () / GetClass (). Para seu uso veja aqui .
Digamos que a hora em que RegisterClass ([TmyClass]) é chamada para registrar uma determinada classe não possa ser prevista lendo o código (é chamado em um método de clique no botão chamado pelo usuário), o código que chama GetClass ('TmyClass') será resultado ou não. A chamada para RegisterClass () não precisa estar no escopo lexical da unidade usando GetClass ();
Outra possibilidade de escopo dinâmico são os métodos anônimos (encerramentos) no Delphi 2009, pois eles conhecem as variáveis de sua função de chamada. Ele não segue o caminho de chamada de lá recursivamente e, portanto, não é totalmente dinâmico.
Adoro as respostas totalmente independentes e independentes do idioma de pessoas como @Arak. Como essa pergunta foi marcada como JavaScript , gostaria de incluir algumas notas muito específicas para esse idioma.
Em JavaScript, nossas opções de escopo são:
var _this = this; function callback(){ console.log(_this); }
callback.bind(this)
Vale a pena notar, eu acho, que o JavaScript realmente não tem escopo dinâmico . .bind
ajusta a this
palavra - chave e isso é próximo, mas tecnicamente não é o mesmo.
Aqui está um exemplo demonstrando as duas abordagens. Você faz isso toda vez que toma uma decisão sobre o escopo de retornos de chamada, para que isso se aplique a promessas, manipuladores de eventos e muito mais.
Aqui está o que você pode chamar Lexical Scoping
de retorno de chamada em JavaScript:
var downloadManager = {
initialize: function() {
var _this = this; // Set up `_this` for lexical access
$('.downloadLink').on('click', function () {
_this.startDownload();
});
},
startDownload: function(){
this.thinking = true;
// Request the file from the server and bind more callbacks for when it returns success or failure
}
//...
};
Outra maneira de escopo é usar Function.prototype.bind
:
var downloadManager = {
initialize: function() {
$('.downloadLink').on('click', function () {
this.startDownload();
}.bind(this)); // Create a function object bound to `this`
}
//...
Esses métodos são, até onde eu sei, comportamentalmente equivalentes.
bind
não afeta o escopo.
A IBM define como:
A parte de uma unidade de programa ou segmento na qual uma declaração se aplica. Um identificador declarado em uma rotina é conhecido nessa rotina e em todas as rotinas aninhadas. Se uma rotina aninhada declarar um item com o mesmo nome, o item externo não estará disponível na rotina aninhada.
Exemplo 1:
function x() {
/*
Variable 'a' is only available to function 'x' and function 'y'.
In other words the area defined by 'x' is the lexical scope of
variable 'a'
*/
var a = "I am a";
function y() {
console.log( a )
}
y();
}
// outputs 'I am a'
x();
Exemplo 2:
function x() {
var a = "I am a";
function y() {
/*
If a nested routine declares an item with the same name,
the outer item is not available in the nested routine.
*/
var a = 'I am inner a';
console.log( a )
}
y();
}
// outputs 'I am inner a'
x();
O escopo léxico significa que, em um grupo aninhado de funções, as funções internas têm acesso às variáveis e outros recursos do escopo pai . Isso significa que as funções filho estão lexicamente ligadas ao contexto de execução de seus pais. Às vezes, o escopo léxico também é chamado de escopo estático .
function grandfather() {
var name = 'Hammad';
// 'likes' is not accessible here
function parent() {
// 'name' is accessible here
// 'likes' is not accessible here
function child() {
// Innermost level of the scope chain
// 'name' is also accessible here
var likes = 'Coding';
}
}
}
O que você notará sobre o escopo lexical é que ele funciona adiante, significando que o nome pode ser acessado pelos contextos de execução de seus filhos. Mas não funciona de volta para os pais, o que significa que a variável likes
não pode ser acessada pelos pais.
Isso também nos diz que variáveis com o mesmo nome em diferentes contextos de execução ganham precedência de cima para baixo da pilha de execução. Uma variável, com um nome semelhante a outra variável, na função mais interna (contexto mais alto da pilha de execução) terá maior precedência.
Observe que isso é retirado daqui .
Em linguagem simples, o escopo lexical é uma variável definida fora do seu escopo ou o escopo superior está automaticamente disponível dentro do seu escopo, o que significa que você não precisa passá-lo para lá.
Exemplo:
let str="JavaScript";
const myFun = () => {
console.log(str);
}
myFun();
// Saída: JavaScript
bind
. Com eles, o bind
não é mais necessário. Para obter mais informações sobre essa alteração, verifique stackoverflow.com/a/34361380/11127383
Falta uma parte importante da conversa em torno do escopo lexical e dinâmico : uma explicação clara do tempo de vida da variável com escopo - ou quando a variável pode ser acessada.
O escopo dinâmico corresponde apenas muito pouco ao escopo "global" da maneira como tradicionalmente pensamos (a razão pela qual eu trago a comparação entre os dois é que ele já foi mencionado - e não gosto particularmente da explicação do artigo vinculado ); provavelmente é melhor não fazermos a comparação entre global e dinâmica - embora supostamente, de acordo com o artigo vinculado, "... [seja] útil como um substituto para variáveis de escopo global".
Então, em inglês simples, qual é a distinção importante entre os dois mecanismos de escopo?
O escopo léxico foi definido muito bem nas respostas acima: variáveis de escopo lexicamente estão disponíveis - ou acessíveis - no nível local da função em que foram definidas.
No entanto - como não é o foco do OP - o escopo dinâmico não recebeu muita atenção e a atenção que recebeu significa que provavelmente precisa de um pouco mais (isso não é uma crítica a outras respostas, mas sim um "oh, essa resposta nos fez desejar que houvesse um pouco mais "). Então, aqui está um pouco mais:
O escopo dinâmico significa que uma variável é acessível ao programa maior durante a vida útil da chamada de função - ou enquanto a função está em execução. Realmente, a Wikipedia realmente faz um bom trabalho com a explicação da diferença entre os dois. Para não ofuscá-lo, eis o texto que descreve o escopo dinâmico:
... [n] escopo dinâmico (ou escopo dinâmico), se o escopo de um nome de variável for uma determinada função, seu escopo será o período de tempo durante o qual a função está executando: enquanto a função está em execução, o nome da variável existe e está vinculado à sua variável, mas após o retorno da função, o nome da variável não existe.
O escopo léxico significa que uma função procura variáveis no contexto em que foi definida, e não no escopo imediatamente ao seu redor.
Veja como o escopo lexical funciona no Lisp, se você quiser mais detalhes. A resposta selecionada por Kyle Cronin em variáveis dinâmicas e lexicais no Common Lisp é muito mais clara do que as respostas aqui.
Coincidentemente, eu só aprendi sobre isso em uma classe Lisp, e isso também se aplica ao JavaScript.
Eu executei esse código no console do Chrome.
// JavaScript Equivalent Lisp
var x = 5; //(setf x 5)
console.debug(x); //(print x)
function print_x(){ //(defun print-x ()
console.debug(x); // (print x)
} //)
(function(){ //(let
var x = 10; // ((x 10))
console.debug(x); // (print x)
print_x(); // (print-x)
})(); //)
Resultado:
5
10
5
Um escopo lexical em JavaScript significa que uma variável definida fora de uma função pode ser acessada dentro de outra função definida após a declaração da variável. Mas o contrário não é verdadeiro; as variáveis definidas dentro de uma função não estarão acessíveis fora dessa função.
Esse conceito é muito usado em fechamentos em JavaScript.
Digamos que temos o código abaixo.
var x = 2;
var add = function() {
var y = 1;
return x + y;
};
Agora, quando você chama add () -> isso imprimirá 3.
Portanto, a função add () está acessando a variável global x
que é definida antes do método function add. Isso é chamado devido ao escopo lexical no JavaScript.
add()
função fosse chamada imediatamente após o trecho de código fornecido, ela também imprimiria 3. O escopo lexical não significa simplesmente que uma função pode acessar variáveis globais fora do contexto local. Portanto, o código de exemplo realmente não ajuda a mostrar o significado do escopo lexical. Mostrar o escopo lexical no código realmente precisa de um contra-exemplo ou pelo menos uma explicação de outras possíveis interpretações do código.
O escopo lexical refere-se ao léxico dos identificadores (por exemplo, variáveis, funções, etc.) visíveis a partir da posição atual na pilha de execução.
- global execution context
- foo
- bar
- function1 execution context
- foo2
- bar2
- function2 execution context
- foo3
- bar3
foo
e bar
estão sempre dentro do léxico dos identificadores disponíveis porque são globais.
Quando function1
é executado, ele tem acesso a um léxico de foo2
, bar2
, foo
, e bar
.
Quando function2
é executado, ele tem acesso a um léxico de foo3
, bar3
, foo2
, bar2
, foo
, e bar
.
A razão pela qual funções globais e / ou externas não têm acesso a identificadores de funções internas é porque a execução dessa função ainda não ocorreu e, portanto, nenhum de seus identificadores foi alocado para a memória. Além disso, uma vez que o contexto interno é executado, ele é removido da pilha de execução, o que significa que todos os seus identificadores foram coletados como lixo e não estão mais disponíveis.
Finalmente, é por isso que um contexto de execução aninhado SEMPRE pode acessar seu contexto de execução de ancestrais e, portanto, por que ele tem acesso a um léxico maior de identificadores.
Vejo:
Agradecimentos especiais a @ robr3rd pela ajuda na simplificação da definição acima.
Aqui está um ângulo diferente sobre essa questão que podemos obter dando um passo atrás e analisando o papel do escopo na estrutura maior de interpretação (executando um programa). Em outras palavras, imagine que você estava construindo um intérprete (ou compilador) para uma linguagem e foi responsável por calcular a saída, dado um programa e alguma entrada para ele.
Interpretação envolve acompanhar três coisas:
Estado - ou seja, variáveis e locais de memória referenciados na pilha e na pilha.
Operações nesse estado - ou seja, todas as linhas de código do seu programa
O ambiente em que uma determinada operação é executada - a saber, a projeção do estado em uma operação.
Um intérprete inicia na primeira linha de código de um programa, calcula seu ambiente, executa a linha nesse ambiente e captura seu efeito no estado do programa. Em seguida, segue o fluxo de controle do programa para executar a próxima linha de código e repete o processo até o término do programa.
A maneira como você calcula o ambiente para qualquer operação é através de um conjunto formal de regras definidas pela linguagem de programação. O termo "ligação" é frequentemente usado para descrever o mapeamento do estado geral do programa para um valor no ambiente. Observe que por "estado geral" não queremos dizer estado global, mas sim a soma total de todas as definições alcançáveis, em qualquer ponto da execução).
Essa é a estrutura na qual o problema de escopo é definido. Agora, para a próxima parte do que são nossas opções.
Esta é a essência do escopo dinâmico , em que o ambiente em que qualquer código é executado é vinculado ao estado do programa, conforme definido pelo seu contexto de execução.
Em outras palavras, com escopo lexical, o ambiente que qualquer código vê é vinculado ao estado associado a um escopo definido explicitamente na linguagem, como um bloco ou uma função.
Pergunta antiga, mas aqui está minha opinião.
O escopo lexical (estático) refere-se ao escopo de uma variável no código-fonte .
Em uma linguagem como JavaScript, onde as funções podem ser passadas, anexadas e reconectadas a objetos diversos, você pode pensar que esse escopo depende de quem está chamando a função no momento, mas não. Alterar o escopo dessa maneira seria um escopo dinâmico, e o JavaScript não faz isso, exceto possivelmente com othis
referência objeto.
Para ilustrar o ponto:
var a='apple';
function doit() {
var a='aardvark';
return function() {
alert(a);
}
}
var test=doit();
test();
No exemplo, a variável a
é definida globalmente, mas sombreada na doit()
função. Esta função retorna outra função que, como você vê, depende doa
variável fora de seu próprio escopo.
Se você executar isso, descobrirá que o valor usado é aardvark
, não o apple
qual, embora esteja no escopo dotest()
função, não está no escopo lexical da função original. Ou seja, o escopo usado é o escopo que aparece no código-fonte, não o escopo em que a função é realmente usada.
Esse fato pode ter consequências irritantes. Por exemplo, você pode decidir que é mais fácil organizar suas funções separadamente e usá-las quando chegar a hora, como em um manipulador de eventos:
var a='apple',b='banana';
function init() {
var a='aardvark',b='bandicoot';
document.querySelector('button#a').onclick=function(event) {
alert(a);
}
document.querySelector('button#b').onclick=doB;
}
function doB(event) {
alert(b);
}
init();
<button id="a">A</button>
<button id="b">B</button>
Este exemplo de código faz um de cada. Você pode ver que, devido ao escopo lexical, o botão A
usa a variável interna, enquanto o botãoB
não. Você pode acabar aninhando funções mais do que gostaria.
A propósito, nos dois exemplos, você também observará que as variáveis internas com escopo lexicamente persistem, mesmo que a função da função que contém tenha executado seu curso. Isso é chamado de fechamento e refere-se ao acesso de uma função aninhada a variáveis externas, mesmo que a função externa tenha sido concluída. O JavaScript precisa ser inteligente o suficiente para determinar se essas variáveis não são mais necessárias e, se não, podem ser coletadas pelo lixo.
Normalmente aprendo pelo exemplo, e aqui está uma coisinha:
const lives = 0;
function catCircus () {
this.lives = 1;
const lives = 2;
const cat1 = {
lives: 5,
jumps: () => {
console.log(this.lives);
}
};
cat1.jumps(); // 1
console.log(cat1); // { lives: 5, jumps: [Function: jumps] }
const cat2 = {
lives: 5,
jumps: () => {
console.log(lives);
}
};
cat2.jumps(); // 2
console.log(cat2); // { lives: 5, jumps: [Function: jumps] }
const cat3 = {
lives: 5,
jumps: () => {
const lives = 3;
console.log(lives);
}
};
cat3.jumps(); // 3
console.log(cat3); // { lives: 5, jumps: [Function: jumps] }
const cat4 = {
lives: 5,
jumps: function () {
console.log(lives);
}
};
cat4.jumps(); // 2
console.log(cat4); // { lives: 5, jumps: [Function: jumps] }
const cat5 = {
lives: 5,
jumps: function () {
var lives = 4;
console.log(lives);
}
};
cat5.jumps(); // 4
console.log(cat5); // { lives: 5, jumps: [Function: jumps] }
const cat6 = {
lives: 5,
jumps: function () {
console.log(this.lives);
}
};
cat6.jumps(); // 5
console.log(cat6); // { lives: 5, jumps: [Function: jumps] }
const cat7 = {
lives: 5,
jumps: function thrownOutOfWindow () {
console.log(this.lives);
}
};
cat7.jumps(); // 5
console.log(cat7); // { lives: 5, jumps: [Function: thrownOutOfWindow] }
}
catCircus();
Este tópico está fortemente relacionado à bind
função interna e introduzido nas funções de seta do ECMAScript 6 . Foi realmente irritante, porque para cada novo método de "classe" (função realmente) que queríamos usar, precisávamos bind
disso para ter acesso ao escopo.
O JavaScript por padrão não definir seu escopo de this
em funções (que não define o contexto em this
). Por padrão, você precisa dizer explicitamente qual contexto deseja ter.
As funções de seta obtêm automaticamente o chamado escopo lexical (têm acesso à definição da variável em seu bloco contendo). Ao usar as funções de seta, ele se liga automaticamente this
ao local em que a função de seta foi definida em primeiro lugar, e o contexto dessas funções de seta é seu bloco contendo.
Veja como ele funciona na prática nos exemplos mais simples abaixo.
Antes das funções de seta (sem escopo lexical por padrão):
const programming = {
language: "JavaScript",
getLanguage: function() {
return this.language;
}
}
const globalScope = programming.getLanguage;
console.log(globalScope()); // Output: undefined
const localScope = programming.getLanguage.bind(programming);
console.log(localScope()); // Output: "JavaScript"
Com funções de seta (escopo lexical por padrão):
const programming = {
language: "JavaScript",
getLanguage: function() {
return this.language;
}
}
const arrowFunction = () => {
console.log(programming.getLanguage());
}
arrowFunction(); // Output: "JavaScript"