¿Qué es una breve introducción al alcance léxico?
¿Qué es una breve introducción al alcance léxico?
Respuestas:
Los entiendo a través de ejemplos. :)
Primero, el alcance léxico (también llamado alcance estático ), en sintaxis tipo C:
void fun()
{
int x = 5;
void fun2()
{
printf("%d", x);
}
}
Cada nivel interno puede acceder a sus niveles externos.
Hay otra forma, llamada ámbito dinámico utilizado por la primera implementación de Lisp , nuevamente en una sintaxis tipo C:
void fun()
{
printf("%d", x);
}
void dummy1()
{
int x = 5;
fun();
}
void dummy2()
{
int x = 10;
fun();
}
Aquí fun
puede acceder x
en dummy1
o dummy2
, o x
en cualquier función que llame fun
con x
declarada en él.
dummy1();
imprimirá 5,
dummy2();
imprimirá 10.
El primero se llama estático porque se puede deducir en tiempo de compilación, y el segundo se llama dinámico porque el alcance externo es dinámico y depende de la llamada en cadena de las funciones.
Encuentro el alcance estático más fácil para el ojo. La mayoría de los idiomas fueron así finalmente, incluso Lisp (puede hacer ambas cosas, ¿verdad?). El alcance dinámico es como pasar referencias de todas las variables a la función llamada.
Como ejemplo de por qué el compilador no puede deducir el alcance dinámico externo de una función, considere nuestro último ejemplo. Si escribimos algo como esto:
if(/* some condition */)
dummy1();
else
dummy2();
La cadena de llamadas depende de una condición de tiempo de ejecución. Si es cierto, entonces la cadena de llamadas se ve así:
dummy1 --> fun()
Si la condición es falsa:
dummy2 --> fun()
El alcance externo de fun
en ambos casos es la persona que llama más la persona que llama y así sucesivamente .
Solo por mencionar que el lenguaje C no permite funciones anidadas ni ámbito dinámico.
JavaScript
. Por lo tanto, creo que esto no debería marcarse como la respuesta aceptada. El alcance léxico específicamente en JS es diferente
for
bucle se encuentra el problema típico. El alcance léxico para JavaScript solo está en el nivel de la función a menos que se use el ES6 let
o const
.
Probemos la definición más corta posible:
El alcance léxico define cómo se resuelven los nombres de las variables en funciones anidadas: las funciones internas contienen el alcance de las funciones principales, incluso si la función principal ha regresado .
¡Eso es todo!
var scope = "I am global";
function whatismyscope(){
var scope = "I am just a local";
function func() {return scope;}
return func;
}
whatismyscope()()
El código anterior devolverá "Solo soy un local". No devolverá "Soy un global". Porque la función func () cuenta dónde se definió originalmente, que está bajo el alcance de la función whatismyscope.
No se molestará por lo que se llame (el alcance global / incluso dentro de otra función), es por eso que el valor del alcance global que soy global no se imprimirá.
Esto se denomina alcance léxico donde "las funciones se ejecutan utilizando la cadena de alcance que estaba vigente cuando se definieron ", según la Guía de definición de JavaScript.
El alcance léxico es un concepto muy muy poderoso.
Espero que esto ayude..:)
El alcance léxico (AKA estático) se refiere a la determinación del alcance de una variable basándose únicamente en su posición dentro del corpus textual de código. Una variable siempre se refiere a su entorno de nivel superior. Es bueno entenderlo en relación con el alcance dinámico.
El alcance define el área, donde las funciones, variables y demás están disponibles. La disponibilidad de una variable, por ejemplo, se define dentro de su contexto, digamos la función, archivo u objeto en el que se definen. Generalmente llamamos a estas variables locales.
La parte léxica significa que puede derivar el alcance de la lectura del código fuente.
El alcance léxico también se conoce como alcance estático.
El alcance dinámico define variables globales a las que se puede llamar o hacer referencia desde cualquier lugar después de definirse. A veces se denominan variables globales, aunque las variables globales en la mayoría de los lenguajes de programa son de alcance léxico. Esto significa que puede derivarse de la lectura del código que la variable está disponible en este contexto. Tal vez uno tiene que seguir una cláusula de usos o inclusiones para encontrar la instanciación o definición, pero el código / compilador conoce la variable en este lugar.
En el ámbito dinámico, por el contrario, primero busca en la función local, luego busca en la función que llamó a la función local, luego busca en la función que llamó a esa función, y así sucesivamente, sube la pila de llamadas. "Dinámico" se refiere al cambio, ya que la pila de llamadas puede ser diferente cada vez que se llama a una función determinada, por lo que la función puede afectar a diferentes variables dependiendo de dónde se llama. (ver aquí )
Para ver un ejemplo interesante de alcance dinámico, consulte aquí .
Para más detalles ver aquí y aquí .
Algunos ejemplos en Delphi / Object Pascal
Delphi tiene alcance léxico.
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.
Lo más cercano que Delphi llega al ámbito dinámico es el par de funciones RegisterClass () / GetClass (). Para su uso ver aquí .
Digamos que el tiempo que se llama a RegisterClass ([TmyClass]) para registrar una determinada clase no se puede predecir leyendo el código (se llama en un método de clic de botón llamado por el usuario), se obtendrá el código que llama a GetClass ('TmyClass') un resultado o no La llamada a RegisterClass () no tiene que estar en el ámbito léxico de la unidad usando GetClass ();
Otra posibilidad de alcance dinámico son los métodos anónimos (cierres) en Delphi 2009, ya que conocen las variables de su función de llamada. No sigue la ruta de llamada desde allí de forma recursiva y, por lo tanto, no es completamente dinámica.
Me encantan las respuestas completas y agnósticas del lenguaje de personas como @Arak. Sin embargo, dado que esta pregunta fue etiquetada con JavaScript , me gustaría incluir algunas notas muy específicas de este lenguaje.
En JavaScript nuestras opciones para el alcance son:
var _this = this; function callback(){ console.log(_this); }
callback.bind(this)
Vale la pena señalar, creo, que JavaScript realmente no tiene un alcance dinámico . .bind
ajusta la this
palabra clave, y eso está cerca, pero técnicamente no es lo mismo.
Aquí hay un ejemplo que demuestra ambos enfoques. Hace esto cada vez que toma una decisión sobre cómo abarcar las devoluciones de llamada para que esto se aplique a las promesas, los controladores de eventos y más.
Esto es lo que podría Lexical Scoping
llamar las devoluciones de llamada en 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
}
//...
};
Otra forma de alcance es usar Function.prototype.bind
:
var downloadManager = {
initialize: function() {
$('.downloadLink').on('click', function () {
this.startDownload();
}.bind(this)); // Create a function object bound to `this`
}
//...
Estos métodos son, hasta donde yo sé, conductualmente equivalentes.
bind
no afecta el alcance.
IBM lo define como:
La parte de un programa o unidad de segmento en la que se aplica una declaración. Se conoce un identificador declarado en una rutina dentro de esa rutina y dentro de todas las rutinas anidadas. Si una rutina anidada declara un elemento con el mismo nombre, el elemento externo no está disponible en la rutina anidada.
Ejemplo 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();
Ejemplo 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();
El alcance léxico significa que en un grupo anidado de funciones, las funciones internas tienen acceso a las variables y otros recursos de su alcance principal . Esto significa que las funciones secundarias están vinculadas léxicamente al contexto de ejecución de sus padres. El alcance léxico a veces también se conoce como alcance 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';
}
}
}
Lo que notará sobre el alcance léxico es que funciona hacia adelante, lo que significa que los contextos de ejecución de sus hijos pueden acceder al nombre. Pero no funciona hacia atrás para sus padres, lo que significa que sus padres likes
no pueden acceder a la variable .
Esto también nos dice que las variables que tienen el mismo nombre en diferentes contextos de ejecución ganan prioridad de arriba a abajo de la pila de ejecución. Una variable, que tiene un nombre similar a otra variable, en la función más interna (contexto superior de la pila de ejecución) tendrá mayor prioridad.
Tenga en cuenta que esto se toma desde aquí .
En un lenguaje simple, el alcance léxico es una variable definida fuera de su alcance o el alcance superior está disponible automáticamente dentro de su alcance, lo que significa que no necesita pasarlo allí.
Ejemplo:
let str="JavaScript";
const myFun = () => {
console.log(str);
}
myFun();
// Salida: JavaScript
bind
. Con ellos, bind
ya no se requiere. Para obtener más información sobre este cambio, consulte stackoverflow.com/a/34361380/11127383
Falta una parte importante de la conversación sobre el alcance dinámico y léxico : una explicación simple de la vida útil de la variable de alcance, o cuándo se puede acceder a la variable.
El alcance dinámico solo se corresponde con el alcance "global" en la forma en que tradicionalmente lo pensamos (la razón por la que menciono la comparación entre ambos es que ya se ha mencionado , y no me gusta particularmente la explicación del artículo vinculado ); probablemente sea mejor que no hagamos la comparación entre global y dinámico, aunque supuestamente, según el artículo vinculado, "... [es] útil como un sustituto de las variables de alcance global".
Entonces, en inglés simple, ¿cuál es la distinción importante entre los dos mecanismos de alcance?
El alcance léxico se ha definido muy bien en todas las respuestas anteriores: las variables con alcance léxico están disponibles, o son accesibles, en el nivel local de la función en la que se definió.
Sin embargo, como no es el foco del OP, el alcance dinámico no ha recibido mucha atención y la atención que ha recibido significa que probablemente necesite un poco más (eso no es una crítica de otras respuestas, sino más bien un "oh, esa respuesta nos hizo desear que hubiera un poco más "). Entonces, aquí hay un poco más:
El alcance dinámico significa que el programa más grande puede acceder a una variable durante la vida útil de la llamada a la función, o mientras la función se está ejecutando. Realmente, Wikipedia realmente hace un buen trabajo con la explicación de la diferencia entre los dos. Para no ofuscarlo, aquí está el texto que describe el alcance dinámico:
... [I] n ámbito dinámico (o ámbito dinámico), si el ámbito de un nombre de variable es una función determinada, entonces su ámbito es el período de tiempo durante el cual la función se está ejecutando: mientras la función se está ejecutando, el nombre de la variable existe , y está vinculado a su variable, pero una vez que la función vuelve, el nombre de la variable no existe.
El alcance léxico significa que una función busca variables en el contexto en el que se definió, y no en el alcance inmediatamente a su alrededor.
Mire cómo funciona el alcance léxico en Lisp si desea más detalles. La respuesta seleccionada por Kyle Cronin en variables dinámicas y léxicas en Common Lisp es mucho más clara que las respuestas aquí.
Casualmente, solo aprendí sobre esto en una clase de Lisp, y sucede que también se aplica en JavaScript.
Ejecuté este código en la consola de 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)
})(); //)
Salida:
5
10
5
Un alcance léxico en JavaScript significa que una variable definida fuera de una función puede ser accesible dentro de otra función definida después de la declaración de la variable. Pero lo contrario no es cierto; las variables definidas dentro de una función no serán accesibles fuera de esa función.
Este concepto se usa mucho en cierres en JavaScript.
Digamos que tenemos el siguiente código.
var x = 2;
var add = function() {
var y = 1;
return x + y;
};
Ahora, cuando llame a add () -> esto imprimirá 3.
Entonces, la función add () está accediendo a la variable global x
que se define antes de la función add del método. Esto se llama debido al alcance léxico en JavaScript.
add()
función se llamara inmediatamente después del fragmento de código dado, también se imprimiría 3. El alcance léxico no significa simplemente que una función pueda acceder a variables globales fuera del contexto local. Entonces, el código de ejemplo realmente no ayuda a mostrar lo que significa el alcance léxico. Mostrar el alcance léxico en el código realmente necesita un contraejemplo o al menos una explicación de otras posibles interpretaciones del código.
El alcance léxico se refiere al léxico de identificadores (por ejemplo, variables, funciones, etc.) visibles desde la posición actual en la pila de ejecución.
- global execution context
- foo
- bar
- function1 execution context
- foo2
- bar2
- function2 execution context
- foo3
- bar3
foo
y bar
siempre están dentro del léxico de los identificadores disponibles porque son globales.
Cuando function1
se ejecuta, tiene acceso a un léxico de foo2
, bar2
, foo
, y bar
.
Cuando function2
se ejecuta, tiene acceso a un léxico de foo3
, bar3
, foo2
, bar2
, foo
, y bar
.
La razón por la que las funciones globales y / o externas no tienen acceso a los identificadores de funciones internas es porque la ejecución de esa función aún no se ha producido y, por lo tanto, ninguno de sus identificadores se ha asignado a la memoria. Además, una vez que se ejecuta ese contexto interno, se elimina de la pila de ejecución, lo que significa que todos sus identificadores se han recolectado basura y ya no están disponibles.
Finalmente, esta es la razón por la cual un contexto de ejecución anidado SIEMPRE puede acceder al contexto de ejecución de sus antepasados y, por lo tanto, tiene acceso a un léxico mayor de identificadores.
Ver:
Un agradecimiento especial a @ robr3rd por ayudarnos a simplificar la definición anterior.
Aquí hay un ángulo diferente sobre esta cuestión que podemos obtener dando un paso atrás y observando el papel del alcance en el marco de interpretación más amplio (ejecutar un programa). En otras palabras, imagine que está construyendo un intérprete (o compilador) para un idioma y que es responsable de calcular la salida, dado un programa y algunas entradas.
La interpretación implica hacer un seguimiento de tres cosas:
Estado: es decir, variables y ubicaciones de memoria referenciadas en el montón y la pila.
Operaciones en ese estado, es decir, cada línea de código en su programa
El entorno en el que se ejecuta una operación determinada , es decir, la proyección de estado en una operación.
Un intérprete comienza en la primera línea de código de un programa, calcula su entorno, ejecuta la línea en ese entorno y captura su efecto en el estado del programa. Luego sigue el flujo de control del programa para ejecutar la siguiente línea de código y repite el proceso hasta que finaliza el programa.
La forma de calcular el entorno para cualquier operación es a través de un conjunto formal de reglas definidas por el lenguaje de programación. El término "enlace" se usa con frecuencia para describir la asignación del estado general del programa a un valor en el entorno. Tenga en cuenta que por "estado general" no nos referimos al estado global, sino a la suma total de cada definición accesible, en cualquier punto de la ejecución).
Este es el marco en el que se define el problema de alcance. Ahora a la siguiente parte de cuáles son nuestras opciones.
Esta es la esencia del alcance dinámico , en el que el entorno en el que se ejecuta cualquier código está vinculado al estado del programa según lo definido por su contexto de ejecución.
En otras palabras, con el alcance léxico, el entorno que ve cualquier código está vinculado al estado asociado con un alcance definido explícitamente en el lenguaje, como un bloque o una función.
Antigua pregunta, pero aquí está mi opinión al respecto.
El alcance léxico (estático) se refiere al alcance de una variable en el código fuente .
En un lenguaje como JavaScript, donde las funciones se pueden pasar y adjuntar y volver a adjuntar a objetos diversos, es posible que ese alcance dependa de quién llama a la función en ese momento, pero no es así. Cambiar el alcance de esa manera sería un alcance dinámico, y JavaScript no lo hace, excepto posiblemente con la this
referencia del objeto.
Para ilustrar el punto:
var a='apple';
function doit() {
var a='aardvark';
return function() {
alert(a);
}
}
var test=doit();
test();
En el ejemplo, la variable a
se define globalmente, pero está sombreada en la doit()
función. Esta función devuelve otra función que, como puede ver, se basa en la a
variable fuera de su propio alcance.
Si ejecuta esto, encontrará que el valor utilizado aardvark
no es el apple
que, aunque está dentro del alcance de la test()
función, no está dentro del alcance léxico de la función original. Es decir, el alcance utilizado es el alcance tal como aparece en el código fuente, no el alcance donde se usa realmente la función.
Este hecho puede tener consecuencias molestas. Por ejemplo, puede decidir que es más fácil organizar sus funciones por separado y luego usarlas cuando llegue el momento, como en un controlador 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 ejemplo de código hace uno de cada uno. Puede ver que debido al alcance léxico, el botón A
usa la variable interna, mientras que el botón B
no. Puede terminar anidando funciones más de lo que le hubiera gustado.
Por cierto, en ambos ejemplos, también notará que las variables de ámbito léxico interno persisten a pesar de que la función de función que contiene ha seguido su curso. Esto se llama cierre y se refiere al acceso de una función anidada a variables externas, incluso si la función externa ha finalizado. JavaScript debe ser lo suficientemente inteligente como para determinar si esas variables ya no son necesarias, y si no, puede recolectar basura.
Normalmente aprendo con el ejemplo, y aquí hay algo:
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 tema está estrechamente relacionado con la bind
función incorporada e introducido en ECMAScript 6 Funciones de flecha . Fue realmente molesto, porque para cada nuevo método de "clase" (función en realidad) que queríamos usar, teníamos que hacerlo bind
para tener acceso al alcance.
JavaScript por defecto no establece su alcance de this
funciones (no establece el contexto en this
). Por defecto, debe decir explícitamente qué contexto desea tener.
Las funciones de flecha obtienen automáticamente el llamado alcance léxico (tienen acceso a la definición de la variable en su bloque contenedor). Cuando se utilizan funciones de flecha, se une automáticamente this
al lugar donde se definió la función de flecha en primer lugar, y el contexto de estas funciones de flecha es su bloque contenedor.
Vea cómo funciona en la práctica en los ejemplos más simples a continuación.
Antes de las funciones de flecha (sin alcance léxico por defecto):
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"
Con funciones de flecha (alcance léxico por defecto):
const programming = {
language: "JavaScript",
getLanguage: function() {
return this.language;
}
}
const arrowFunction = () => {
console.log(programming.getLanguage());
}
arrowFunction(); // Output: "JavaScript"