¿Cuál es el alcance de las variables en JavaScript?

2013

¿Cuál es el alcance de las variables en javascript? ¿Tienen el mismo alcance dentro que fuera de una función? ó acaso importa? Además, ¿dónde se almacenan las variables si están definidas globalmente?

lYriCAlsSH
fuente
44
Aquí hay otro buen enlace para tener en cuenta este problema: " Explicación del alcance y los cierres de JavaScript ".
Ingeniero de software Full-Stack
99
Aquí hay un artículo que lo explica muy bien. Todo lo que necesita saber sobre el alcance variable de Javascript
Saurab Parakh
2
El libro electrónico mencionado anteriormente de Kyle Simpson está disponible para leer en Github, y le dice todo lo que necesita saber sobre JavaScript Scopes & Closures. Puedes encontrarlo aquí: github.com/getify/You-Dont-Know-JS/blob/master/… Es parte de la serie de libros "No sabes JS" , que es genial para todos los que quieran saber Más información sobre JavaScript.
3rik82

Respuestas:

2536

TLDR

JavaScript tiene ámbitos y cierres léxicos (también llamados estáticos). Esto significa que puede determinar el alcance de un identificador mirando el código fuente.

Los cuatro ámbitos son:

  1. Global: visible por todo
  2. Función: visible dentro de una función (y sus subfunciones y bloques)
  3. Bloque: visible dentro de un bloque (y sus subbloques)
  4. Módulo: visible dentro de un módulo

Fuera de los casos especiales de alcance global y de módulo, las variables se declaran usando var(alcance de función), let(alcance de bloque) yconst (alcance de bloque). La mayoría de las otras formas de declaración de identificador tienen alcance de bloque en modo estricto.

Visión general

El alcance es la región de la base de código sobre la cual un identificador es válido.

Un entorno léxico es un mapeo entre los nombres de los identificadores y los valores asociados con ellos.

El alcance está formado por una anidación vinculada de entornos léxicos, con cada nivel en la anidación correspondiente a un entorno léxico de un contexto de ejecución ancestral.

Estos entornos léxicos vinculados forman una "cadena" de alcance. La resolución del identificador es el proceso de búsqueda a lo largo de esta cadena de un identificador coincidente.

La resolución del identificador solo ocurre en una dirección: hacia afuera. De esta manera, los ambientes léxicos externos no pueden "ver" los ambientes léxicos internos.

Hay tres factores pertinentes para decidir el alcance de un identificador en JavaScript:

  1. Cómo se declaró un identificador
  2. Donde se declaró un identificador
  3. Si está en modo estricto o no estricto

Algunas de las formas en que se pueden declarar los identificadores:

  1. var, letyconst
  2. Parámetros de la función
  3. Capturar parámetro de bloque
  4. Declaraciones de funciones
  5. Expresiones de funciones nombradas
  6. Propiedades definidas implícitamente en el objeto global (es decir, perderse varen modo no estricto)
  7. import declaraciones
  8. eval

Se pueden declarar algunos de los identificadores de ubicaciones:

  1. Contexto global
  2. Cuerpo de la función
  3. Bloque ordinario
  4. La parte superior de una estructura de control (por ejemplo, bucle, if, while, etc.)
  5. Estructura de control del cuerpo
  6. Módulos

Estilos de declaración

var

Los identificadores declarados usando var tienen un alcance de función , aparte de cuando se declaran directamente en el contexto global, en cuyo caso se agregan como propiedades en el objeto global y tienen un alcance global. Hay reglas separadas para su uso eneval funciones.

dejar y const

Identificadores declarados usando lety const tienen alcance de bloque , aparte de cuando se declaran directamente en el contexto global, en cuyo caso tienen alcance global.

Nota: let, consty var están todos izada . Esto significa que su posición lógica de definición es la parte superior de su alcance (bloque o función). Sin embargo, las variables declararon su uso lety constno pueden leerse ni asignarse hasta que el control haya pasado el punto de declaración en el código fuente. El período intermedio se conoce como la zona muerta temporal.

function f() {
    function g() {
        console.log(x)
    }
    let x = 1
    g()
}
f() // 1 because x is hoisted even though declared with `let`!

Nombres de parámetros de funciones

Los nombres de los parámetros de la función están dentro del ámbito de la función. Tenga en cuenta que hay una ligera complejidad en esto. Las funciones declaradas como argumentos predeterminados se cierran sobre la lista de parámetros y no el cuerpo de la función.

Declaraciones de funciones

Las declaraciones de función tienen alcance de bloque en modo estricto y alcance de función en modo no estricto. Nota: el modo no estricto es un conjunto complicado de reglas emergentes basadas en las extravagantes implementaciones históricas de diferentes navegadores.

Expresiones de funciones nombradas

Las expresiones de funciones con nombre tienen un alcance propio (p. Ej., Para fines de recursión).

Propiedades definidas implícitamente en el objeto global

En el modo no estricto, las propiedades definidas implícitamente en el objeto global tienen alcance global, porque el objeto global se encuentra en la parte superior de la cadena de alcance. En modo estricto, estos no están permitidos.

eval

En las evalcadenas, las variables declaradas usando varse colocarán en el alcance actual o, si evalse usa indirectamente, como propiedades en el objeto global.

Ejemplos

Lo siguiente arrojará un ReferenceError porque los nombres x, yy zno tienen significado fuera de la función f.

function f() {
    var x = 1
    let y = 1
    const z = 1
}
console.log(typeof x) // undefined (because var has function scope!)
console.log(typeof y) // undefined (because the body of the function is a block)
console.log(typeof z) // undefined (because the body of the function is a block)

Lo siguiente arrojará un ReferenceError para yy z, pero no para x, porque la visibilidad de xno está limitada por el bloque. Los bloques que definen los cuerpos de las estructuras de control como if, fory while, se comportan de manera similar.

{
    var x = 1
    let y = 1
    const z = 1
}
console.log(x) // 1
console.log(typeof y) // undefined because `y` has block scope
console.log(typeof z) // undefined because `z` has block scope

A continuación, xes visible fuera del bucle porque vartiene alcance de función:

for(var x = 0; x < 5; ++x) {}
console.log(x) // 5 (note this is outside the loop!)

... debido a este comportamiento, debe tener cuidado al cerrar las variables declaradas mediante varbucles. Solo hay una instancia de variablex declara , y se ubica lógicamente fuera del bucle.

Las siguientes impresiones 5, cinco veces, y luego imprime 5por sexta vez para el console.logexterior del bucle:

for(var x = 0; x < 5; ++x) {
    setTimeout(() => console.log(x)) // closes over the `x` which is logically positioned at the top of the enclosing scope, above the loop
}
console.log(x) // note: visible outside the loop

Lo siguiente se imprime undefinedporque xtiene un alcance de bloque. Las devoluciones de llamada se ejecutan una por una de forma asincrónica. Nuevo comportamiento de letlos medios de variables que cada función anónima se cerró sobre una variable diferente llamado x(a diferencia de lo habría hecho con var), y así los números enteros 0a través de 4se imprimen .:

for(let x = 0; x < 5; ++x) {
    setTimeout(() => console.log(x)) // `let` declarations are re-declared on a per-iteration basis, so the closures capture different variables
}
console.log(typeof x) // undefined

Lo siguiente NO arrojará un ReferenceErrorporque la visibilidad de xno está limitada por el bloque; sin embargo, se imprimirá undefinedporque la variable no se ha inicializado (debido a la ifdeclaración).

if(false) {
    var x = 1
}
console.log(x) // here, `x` has been declared, but not initialised

Una variable declarada en la parte superior de un forbucle que se usa letse limita al cuerpo del bucle:

for(let x = 0; x < 10; ++x) {} 
console.log(typeof x) // undefined, because `x` is block-scoped

Lo siguiente arrojará un ReferenceErrorporque la visibilidad de xestá limitada por el bloque:

if(false) {
    let x = 1
}
console.log(typeof x) // undefined, because `x` is block-scoped

Las variables declaradas usando var, leto constestán en el ámbito de los módulos:

// module1.js

var x = 0
export function f() {}

//module2.js

import f from 'module1.js'

console.log(x) // throws ReferenceError

Lo siguiente declarará una propiedad en el objeto global, porque las variables declaradas usando vardentro del contexto global, se agregan como propiedades al objeto global:

var x = 1
console.log(window.hasOwnProperty('x')) // true

lety consten el contexto global no agregue propiedades al objeto global, pero aún tenga alcance global:

let x = 1
console.log(window.hasOwnProperty('x')) // false

Los parámetros de la función pueden considerarse declarados en el cuerpo de la función:

function f(x) {}
console.log(typeof x) // undefined, because `x` is scoped to the function

Los parámetros del bloque de captura están sujetos al cuerpo del bloque de captura:

try {} catch(e) {}
console.log(typeof e) // undefined, because `e` is scoped to the catch block

Las expresiones de funciones con nombre solo tienen un alcance para la expresión misma:

(function foo() { console.log(foo) })()
console.log(typeof foo) // undefined, because `foo` is scoped to its own expression

En el modo no estricto, las propiedades definidas implícitamente en el objeto global tienen un alcance global. En modo estricto, obtienes un error.

x = 1 // implicitly defined property on the global object (no "var"!)

console.log(x) // 1
console.log(window.hasOwnProperty('x')) // true

En modo no estricto, las declaraciones de función tienen alcance de función. En modo estricto tienen alcance de bloque.

'use strict'
{
    function foo() {}
}
console.log(typeof foo) // undefined, because `foo` is block-scoped

Cómo funciona debajo del capó

El alcance se define como la región léxica del código sobre la cual un identificador es válido.

En JavaScript, cada objeto de función tiene una [[Environment]]referencia oculta que es una referencia al entorno léxico del contexto de ejecución. (marco de pila) dentro del cual se creó.

Cuando invocas una función, [[Call]]se llama al método oculto . Este método crea un nuevo contexto de ejecución y establece un vínculo entre el nuevo contexto de ejecución y el entorno léxico del objeto de función. Lo hace copiando el [[Environment]]valor en el objeto de función, en un campo de referencia externo en el entorno léxico del nuevo contexto de ejecución.

Tenga en cuenta que este vínculo entre el nuevo contexto de ejecución y el entorno léxico del objeto de función se denomina cierre .

Por lo tanto, en JavaScript, el alcance se implementa a través de entornos léxicos unidos en una "cadena" por referencias externas. Esta cadena de entornos léxicos se denomina cadena de alcance, y la resolución del identificador se produce buscando en la cadena un identificador coincidente.

Averiguar más .

Tríptico
fuente
280
Ni siquiera está cerca de ser exhaustivo, pero este es quizás el conjunto de trucos de alcance de Javascript que uno debe conocer para incluso LEER efectivamente javascript moderno.
Tríptico
148
Una respuesta altamente calificada, no estoy seguro de por qué. Es solo un montón de ejemplos sin una explicación adecuada, luego parece confundir la herencia del prototipo (es decir, la resolución de la propiedad) con la cadena de alcance (es decir, la resolución variable). Una explicación completa (y precisa) del alcance y la resolución de la propiedad se encuentra en las notas de preguntas frecuentes de comp.lang.javascript .
RobG
109
@RobG Está altamente calificado porque es útil y comprensible para una amplia gama de programadores, a pesar de la catacresis menor. El enlace que ha publicado, aunque es útil para algunos profesionales, es incomprensible para la mayoría de las personas que escriben Javascript hoy. Siéntase libre de solucionar cualquier problema de nomenclatura editando la respuesta.
Tríptico
77
@ tríptico: solo edito respuestas para arreglar cosas menores, no mayores. Cambiar "alcance" a "propiedad" arreglará el error, pero no el problema de mezclar herencia y alcance sin una distinción muy clara.
RobG
24
Si define una variable en el ámbito externo y luego tiene una instrucción if, defina una variable dentro de la función con el mismo nombre, incluso si no se alcanza la rama , se redefine. Un ejemplo - jsfiddle.net/3CxVm
Chris S
233

Javascript usa cadenas de alcance para establecer el alcance de una función determinada. Normalmente hay un ámbito global, y cada función definida tiene su propio ámbito anidado. Cualquier función definida dentro de otra función tiene un alcance local que está vinculado a la función externa. Siempre es la posición en la fuente que define el alcance.

Un elemento en la cadena de alcance es básicamente un Mapa con un puntero a su alcance principal.

Al resolver una variable, javascript comienza en el ámbito más interno y busca hacia afuera.

krosenvold
fuente
1
Las cadenas de alcance son otro término para los cierres [de memoria] ... para aquellos que leen aquí para aprender / entrar en javascript.
Nueva Alejandría
108

Las variables declaradas globalmente tienen un alcance global. Las variables declaradas dentro de una función tienen un alcance para esa función, y las variables globales de sombra del mismo nombre.

(Estoy seguro de que hay muchas sutilezas que los programadores reales de JavaScript podrán señalar en otras respuestas. En particular, me encontré con esta página sobre lo que thissignifica exactamente en cualquier momento. Espero que este enlace más introductorio sea ​​suficiente para comenzar, aunque .)

Jon Skeet
fuente
77
Tengo miedo incluso de comenzar a responder esta pregunta. Como programador Real Javascript, sé lo rápido que la respuesta podría salirse de control. Buenos artículos.
Tríptico
10
@Triptych: Sé lo que quieres decir acerca de las cosas yendo de las manos, pero por favor agrega una respuesta de todos modos. Obtuve lo anterior solo por hacer un par de búsquedas ... una respuesta escrita por alguien con experiencia real seguramente será mejor. Por favor, corrija cualquiera de mis respuestas, que definitivamente es incorrecta.
Jon Skeet
44
De alguna manera, Jon Skeet es responsable de MI respuesta más popular en Stack Overflow.
Tríptico el
75

JavaScript de la vieja escuela

Tradicionalmente, JavaScript realmente solo tiene dos tipos de alcance:

  1. Alcance global : las variables son conocidas en toda la aplicación, desde el inicio de la aplicación (*)
  2. Alcance funcional : las variables se conocen dentro de la función en la que se declaran, desde el inicio de la función (*)

No voy a dar más detalles sobre esto, ya que hay muchas otras respuestas que explican la diferencia.


JavaScript moderno

Las especificaciones de JavaScript más recientes ahora también permiten un tercer alcance:

  1. Alcance de bloque : los identificadores se "conocen" desde la parte superior del alcance dentro del cual se declaran , pero no se pueden asignar ni desreferenciar (leer) hasta después de la línea de su declaración. Este período intermedio se denomina "zona muerta temporal".

¿Cómo creo variables de alcance de bloque?

Tradicionalmente, creas tus variables así:

var myVariable = "Some text";

Las variables de alcance de bloque se crean así:

let myVariable = "Some text";

Entonces, ¿cuál es la diferencia entre el alcance funcional y el alcance de bloque?

Para comprender la diferencia entre el alcance funcional y el alcance de bloque, considere el siguiente código:

// i IS NOT known here
// j IS NOT known here
// k IS known here, but undefined
// l IS NOT known here

function loop(arr) {
    // i IS known here, but undefined
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here

    for( var i = 0; i < arr.length; i++ ) {
        // i IS known here, and has a value
        // j IS NOT known here
        // k IS known here, but has a value only the second time loop is called
        // l IS NOT known here
    };

    // i IS known here, and has a value
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here

    for( let j = 0; j < arr.length; j++ ) {
        // i IS known here, and has a value
        // j IS known here, and has a value
        // k IS known here, but has a value only the second time loop is called
        // l IS NOT known here
    };

    // i IS known here, and has a value
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here
}

loop([1,2,3,4]);

for( var k = 0; k < arr.length; k++ ) {
    // i IS NOT known here
    // j IS NOT known here
    // k IS known here, and has a value
    // l IS NOT known here
};

for( let l = 0; l < arr.length; l++ ) {
    // i IS NOT known here
    // j IS NOT known here
    // k IS known here, and has a value
    // l IS known here, and has a value
};

loop([1,2,3,4]);

// i IS NOT known here
// j IS NOT known here
// k IS known here, and has a value
// l IS NOT known here

Aquí, podemos ver que nuestra variable jsolo se conoce en el primer bucle for, pero no antes y después. Sin embargo, nuestra variablei es conocida en toda la función.

Además, tenga en cuenta que las variables de ámbito de bloque no se conocen antes de declararse porque no se izan. Tampoco se le permite volver a declarar la misma variable de ámbito de bloque dentro del mismo bloque. Esto hace que las variables de ámbito de bloque sean menos propensas a errores que las variables de ámbito global o funcional, que se izan y que no producen ningún error en caso de declaraciones múltiples.


¿Es seguro usar variables de alcance de bloque hoy?

Si es seguro usarlo hoy o no, depende de su entorno:

  • Si está escribiendo código JavaScript del lado del servidor ( Node.js ), puede usar la letdeclaración de forma segura .

  • Si está escribiendo código JavaScript del lado del cliente y usa un transpilador basado en navegador (como Traceur o babel-standalone ), puede usar la letdeclaración de forma segura , sin embargo, es probable que su código sea todo menos óptimo con respecto al rendimiento.

  • Si está escribiendo código JavaScript del lado del cliente y usa un transpilador basado en Nodo (como el script de shell traceur o Babel ), puede usar la letdeclaración de manera segura . Y debido a que su navegador solo sabrá sobre el código transpilado, los inconvenientes de rendimiento deben ser limitados.

  • Si está escribiendo código JavaScript del lado del cliente y no usa un transpilador, debe considerar la compatibilidad con el navegador.

    Estos son algunos navegadores que no son compatibles leten absoluto:

    • Internet Explorer 10 y menos
    • Firefox 43 y menos
    • Safari 9 y abajo
    • Navegador de Android 4 y versiones inferiores
    • Opera 27 y menos
    • Chome 40 y menos
    • CUALQUIER versión de Opera Mini y Blackberry Browser

ingrese la descripción de la imagen aquí


Cómo realizar un seguimiento del soporte del navegador

Para obtener una descripción actualizada de qué navegadores admiten la letdeclaración al momento de leer esta respuesta, consulte esta Can I Usepágina .


(*) Las variables de ámbito global y funcional pueden inicializarse y utilizarse antes de declararse porque las variables de JavaScript se izan . Esto significa que las declaraciones siempre están en lo más alto del alcance.

John Slegers
fuente
2
"NO SE conoce" es engañoso, porque la variable se declara allí debido a la elevación.
Oriol
El ejemplo anterior es engañoso, las variables 'i' y 'j' no se conocen fuera del bloque. Las variables 'Let' solo tienen alcance en ese bloque en particular, no fuera del bloque. Let tiene otras ventajas también, no puede volver a declarar la variable nuevamente y tiene el alcance léxico.
zakir
1
Esto fue útil, gracias! Creo que sería aún más útil ser específico sobre lo que quiere decir con "JavaScript moderno" y "JavaScript de la vieja escuela"; Creo que estos corresponden a ECMAScript 6 / ES6 / ECMAScript 2015, y a versiones anteriores, respectivamente.
Jon Schneider
1
@ JonSchneider: ¡Correcto! Cuando digo "JavaScript de la vieja escuela", me refiero a hablar sobre ECMAScript 5 y cuando me refiero a "JavaScript moderno", estoy tomando sobre ECMAScript 6 (también conocido como ECMAScript 2015). Sin embargo, no pensé que fuera realmente tan importante entrar en detalles, ya que la mayoría de las personas solo quieren saber (1) cuál es la diferencia entre el alcance de bloque y el alcance funcional, (2) qué navegadores admiten el alcance de bloque y (3) si es seguro usar el alcance del bloque hoy para cualquier proyecto en el que estén trabajando. Así que centré mi respuesta en abordar esos problemas.
John Slegers
1
@JonSchneider: (continuación) Sin embargo, acabo de agregar un enlace a un artículo de Smashing Magazine sobre ES6 / ES2015 para aquellos que desean obtener más información sobre las funciones que se han agregado a JavaScript durante los últimos años ... de cualquier otra persona que Tal vez se pregunte qué quiero decir con "JavaScript moderno".
John Slegers
39

Aquí hay un ejemplo:

<script>

var globalVariable = 7; //==window.globalVariable

function aGlobal( param ) { //==window.aGlobal(); 
                            //param is only accessible in this function
  var scopedToFunction = {
    //can't be accessed outside of this function

    nested : 3 //accessible by: scopedToFunction.nested
  };

  anotherGlobal = {
    //global because there's no `var`
  }; 

}

</script>

Querrás investigar los cierres y cómo usarlos para hacer miembros privados .

geowa4
fuente
26

En "Javascript 1.7" (extensión de Mozilla a Javascript) también se pueden declarar variables de alcance de bloque con la letdeclaración :

 var a = 4;
 let (a = 3) {
   alert(a); // 3
 }
 alert(a);   // 4
kennytm
fuente
2
Sí, pero ¿es seguro de usar? Quiero decir, ¿elegiría de manera realista esta implementación si mi código se ejecutara en WebKit?
IgorGanapolsky
10
@Python: No, WebKit no es compatible let.
kennytm
Supongo que el único uso válido para esto sería si supieras que todos los clientes usarían un navegador Mozilla como el sistema interno de una empresa.
GazB
O si está programando usando el marco XUL, el marco de interfaz de Mozilla donde construye usando css, xml y javascript.
Gerard ONeill
1
¡@GazB incluso esa es una idea horrible! Así que hoy sabes que tus clientes están usando Mozilla y luego sale un nuevo memorando que dice que ahora están usando otra cosa. Es decir, la razón de nuestro sistema de pago chupa ... Debe utilizar IE8 o IE9 y nunca IE10 o Firefox o Chrome, ya que de plano no funcionará ...
buzzsawddog
25

La idea del alcance en JavaScript cuando fue diseñada originalmente por Brendan Eich surgió del lenguaje de script HyperCard HyperTalk .

En este idioma, las pantallas se hicieron de forma similar a una pila de fichas. Había una tarjeta maestra conocida como fondo. Era transparente y puede verse como la carta inferior. Cualquier contenido en esta tarjeta base se compartió con tarjetas colocadas encima de ella. Cada tarjeta colocada en la parte superior tenía su propio contenido que tenía prioridad sobre la tarjeta anterior, pero aún tenía acceso a las tarjetas anteriores si lo deseaba.

Así es exactamente cómo está diseñado el sistema de alcance de JavaScript. Solo tiene diferentes nombres. Las tarjetas en JavaScript se conocen como contextos de ejecución ECMA . Cada uno de estos contextos contiene tres partes principales. Un entorno variable, un entorno léxico y este enlace. Volviendo a la referencia de las cartas, el entorno léxico contiene todo el contenido de las cartas anteriores más abajo en la pila. El contexto actual está en la parte superior de la pila y cualquier contenido declarado allí se almacenará en el entorno variable. El entorno variable tendrá prioridad en el caso de colisiones de nombres.

El enlace this apuntará al objeto contenedor. A veces, los ámbitos o los contextos de ejecución cambian sin que cambie el objeto contenedor, como en una función declarada donde puede estar el objeto contenedor windowo una función constructora.

Estos contextos de ejecución se crean cada vez que se transfiere el control. El control se transfiere cuando el código comienza a ejecutarse, y esto se hace principalmente desde la ejecución de la función.

Esa es la explicación técnica. En la práctica, es importante recordar que en JavaScript

  • Los ámbitos son técnicamente "Contextos de ejecución"
  • Los contextos forman una pila de entornos donde se almacenan las variables.
  • La parte superior de la pila tiene prioridad (la parte inferior es el contexto global)
  • Cada función crea un contexto de ejecución (pero no siempre es nuevo este enlace)

Aplicando esto a uno de los ejemplos anteriores (5. "Cierre") en esta página, es posible seguir la pila de contextos de ejecución. En este ejemplo, hay tres contextos en la pila. Están definidos por el contexto externo, el contexto en la función invocada inmediatamente llamada por var six, y el contexto en la función devuelta dentro de la función invocada inmediatamente por var six.

i ) El contexto externo. Tiene un entorno variable de a = 1
ii ) El contexto IIFE, tiene un entorno léxico de a = 1, pero un entorno variable de a = 6 que tiene prioridad en la pila
iii ) El contexto de la función devuelta, tiene un léxico entorno de a = 6 y ese es el valor referenciado en la alerta cuando se llama.

ingrese la descripción de la imagen aquí

Travis J
fuente
17

1) Existe un ámbito global, un ámbito de función y los ámbitos con y catch. No existe un alcance de nivel de 'bloque' en general para las variables: las declaraciones with y catch agregan nombres a sus bloques.

2) Los ámbitos están anidados por funciones hasta el alcance global.

3) Las propiedades se resuelven pasando por la cadena del prototipo. La instrucción with lleva los nombres de las propiedades del objeto al ámbito léxico definido por el bloque with.

EDITAR: ECMAAScript 6 (Armonía) está especificado para admitir let, y sé que Chrome permite una bandera de "armonía", por lo que tal vez sí lo admite.

Let sería un soporte para el alcance del nivel de bloque, pero debe usar la palabra clave para que suceda.

EDITAR: Basándome en que Benjamin señaló las declaraciones con y en los comentarios, edité la publicación y agregué más. Tanto las declaraciones con como las capturas introducen variables en sus bloques respectivos, y ese es un alcance de bloque. Estas variables tienen un alias a las propiedades de los objetos que se les pasan.

 //chrome (v8)

 var a = { 'test1':'test1val' }
 test1   // error not defined
 with (a) { var test1 = 'replaced' }
 test1   // undefined
 a       // a.test1 = 'replaced'

EDITAR: Ejemplo de aclaración:

test1 tiene un alcance con el bloque with, pero tiene un alias de a.test1. 'Var test1' crea una nueva variable test1 en el contexto léxico superior (función o global), a menos que sea una propiedad de a, que lo es.

¡Ay! Tenga cuidado al usar 'con', al igual que var es un noop si la variable ya está definida en la función, ¡también es un noop con respecto a los nombres importados del objeto! Un pequeño aviso sobre el nombre que ya está definido haría esto mucho más seguro. Personalmente nunca lo usaré debido a esto.

Gerard ONeill
fuente
Aquí tiene algunos errores, ya que un JavaScript tiene formas de alcance de bloque.
Benjamin Gruenbaum
Mis oídos (ojos) están abiertos, Benjamin. Mis declaraciones anteriores son cómo he tratado el alcance de Javascript, pero no se basan en la lectura de la especificación. Y espero que no se esté refiriendo a la declaración with (que es una forma de alcance del objeto), o la sintaxis especial 'let' de Mozilla.
Gerard ONeill
Bueno, la withdeclaración es una forma de alcance de bloque, pero las catchcláusulas son una forma mucho más común (Dato curioso, v8 se implementa catchcon a with): esas son prácticamente las únicas formas de alcance de bloque en JavaScript (es decir, función, global, try / catch , con y sus derivados), sin embargo, los entornos host tienen diferentes nociones de alcance, por ejemplo, eventos en línea en el navegador y el módulo vm de NodeJS.
Benjamin Gruenbaum
Benjamin: por lo que puedo ver, tanto con y catch solo introducen el objeto en el alcance actual (y, por lo tanto, las propiedades), pero luego, después de que finaliza el bloque respectivo, las variables se restablecen. Pero, por ejemplo, una nueva variable introducida en una captura tendrá el alcance de la función / método de cierre.
Gerard ONeill
2
Que es exactamente lo que significa el alcance del bloque :)
Benjamin Gruenbaum
9

Descubrí que muchas personas nuevas en JavaScript tienen problemas para comprender que la herencia está disponible por defecto en el lenguaje y que el alcance de la función es el único alcance, hasta ahora. Proporcioné una extensión a un embellecedor que escribí a fines del año pasado llamado JSPretty. Los colores de función funcionan en el alcance del código y siempre asocian un color a todas las variables declaradas en ese alcance. El cierre se demuestra visualmente cuando una variable con un color de un alcance se usa en un alcance diferente.

Pruebe la función en:

Vea una demostración en:

Ver el código en:

Actualmente, la función ofrece soporte para una profundidad de 16 funciones anidadas, pero actualmente no colorea variables globales.

austincheney
fuente
1
No funciona para mí con Firefox 26. Pego el código o cargo un archivo, hago clic en ejecutar y no sucede nada.
mplwork
El alcance y la herencia son dos cosas diferentes.
Ben Aston
9

JavaScript tiene solo dos tipos de alcance:

  1. Alcance global : Global no es más que un alcance a nivel de ventana. Aquí, variable presente en toda la aplicación.
  2. Alcance funcional : la variable declarada dentro de una función con varpalabra clave tiene alcance funcional.

Cada vez que se llama a una función, se crea un objeto de alcance variable (y se incluye en la cadena de alcance) seguido de variables en JavaScript.

        a = "global";
         function outer(){ 
              b = "local";
              console.log(a+b); //"globallocal"
         }
outer();

Cadena de alcance ->

  1. Nivel de la ventana - ay outerfunción están en el nivel superior en cadena de ámbitos.
  2. cuando la función externa se llama nueva variable scope object(e incluida en la cadena de alcance) agregada con una variable bdentro de ella.

Ahora, cuando se arequiere una variable , primero busca el alcance de la variable más cercana y, si la variable no está allí, se mueve al siguiente objeto de la cadena de alcance de la variable, que en este caso es el nivel de la ventana.

Anshul
fuente
1
No estoy seguro de por qué esta no es la respuesta aceptada. En realidad, solo hay un alcance funcional (antes de ECMA6 no había "alcance local") y enlaces globales
texasbruce
9

Solo para agregar a las otras respuestas, el alcance es una lista de búsqueda de todos los identificadores (variables) declarados, y aplica un conjunto estricto de reglas sobre cómo son accesibles para el código que se está ejecutando actualmente. Esta búsqueda puede ser con el propósito de asignar a la variable, que es una referencia LHS (lado izquierdo), o puede ser con el propósito de recuperar su valor, que es una referencia RHS (lado derecho). Estas búsquedas son lo que el motor de JavaScript hace internamente cuando compila y ejecuta el código.

Desde esta perspectiva, creo que una imagen ayudaría que encontré en el libro electrónico Scopes and Closures de Kyle Simpson:

imagen

Citando de su libro electrónico:

El edificio representa el conjunto de reglas de alcance anidado de nuestro programa. El primer piso del edificio representa su alcance actual en ejecución, esté donde esté. El nivel superior del edificio es el alcance global. Usted resuelve las referencias de LHS y RHS mirando su piso actual, y si no lo encuentra, tome el elevador al siguiente piso, mire allí, luego al siguiente, y así sucesivamente. Una vez que llegue al piso superior (el alcance global), encontrará lo que está buscando o no. Pero tienes que parar de todos modos.

Una cosa de nota que vale la pena mencionar, "La búsqueda del alcance se detiene una vez que encuentra la primera coincidencia".

Esta idea de "niveles de alcance" explica por qué "esto" se puede cambiar con un alcance recién creado, si se busca en una función anidada. Aquí hay un enlace que incluye todos estos detalles. Todo lo que quería saber sobre el alcance de JavaScript.

James Drinkard
fuente
8

ejecuta el código. espero que esto dé una idea sobre el alcance

Name = 'global data';
document.Name = 'current document data';
(function(window,document){
var Name = 'local data';
var myObj = {
    Name: 'object data',
    f: function(){
        alert(this.Name);
    }
};

myObj.newFun = function(){
    alert(this.Name);
}

function testFun(){
    alert("Window Scope : " + window.Name + 
          "\nLocal Scope : " + Name + 
          "\nObject Scope : " + this.Name + 
          "\nCurrent document Scope : " + document.Name
         );
}


testFun.call(myObj);
})(window,document);
Yeasin Abedin Siam
fuente
8

Alcance global :

Las variables globales son exactamente como las estrellas globales (Jackie Chan, Nelson Mandela). Puede acceder a ellos (obtener o establecer el valor), desde cualquier parte de su aplicación. Las funciones globales son como eventos globales (Año Nuevo, Navidad). Puede ejecutarlos (llamarlos) desde cualquier parte de su aplicación.

//global variable
var a = 2;

//global function
function b(){
   console.log(a);  //access global variable
}

Alcance local:

Si estás en los EE. UU., Es posible que conozcas a Kim Kardashian, famosa celebridad (de alguna manera se las arregla para hacer los tabloides). Pero las personas fuera de los Estados Unidos no la reconocerán. Ella es una estrella local, ligada a su territorio.

Las variables locales son como estrellas locales. Solo puede acceder a ellos (obtener o establecer el valor) dentro del alcance. Una función local es como eventos locales: solo puede ejecutar (celebrar) dentro de ese ámbito. Si desea acceder a ellos desde fuera del alcance, obtendrá un error de referencia

function b(){
   var d = 21; //local variable
   console.log(d);

   function dog(){  console.log(a); }
     dog(); //execute local function
}

 console.log(d); //ReferenceError: dddddd is not defined    

Consulte este artículo para conocer en profundidad el alcance

Jhankar Mahbub
fuente
6

Casi solo hay dos tipos de ámbitos de JavaScript:

  • el alcance de cada declaración var está asociado con la función de cierre más inmediata
  • si no hay una función de cierre para una declaración var, es de alcance global

Por lo tanto, los bloques que no sean funciones no crean un nuevo alcance. Eso explica por qué los bucles for sobrescriben las variables de ámbito externo:

var i = 10, v = 10;
for (var i = 0; i < 5; i++) { var v = 5; }
console.log(i, v);
// output 5 5

Usando funciones en su lugar:

var i = 10, v = 10;
$.each([0, 1, 2, 3, 4], function(i) { var v = 5; });
console.log(i,v);
// output 10 10

En el primer ejemplo, no había un alcance de bloque, por lo que las variables inicialmente declaradas se sobrescribieron. En el segundo ejemplo, había un nuevo alcance debido a la función, por lo que las variables inicialmente declaradas se SOMBRARON y no se sobrescribieron.

Eso es casi todo lo que necesita saber en términos de alcance de JavaScript, excepto:

Por lo tanto, puede ver que el alcance de JavaScript es en realidad extremadamente simple, aunque no siempre intuitivo. Algunas cosas a tener en cuenta:

  • las declaraciones var se elevan a la parte superior del alcance. Esto significa que no importa dónde ocurra la declaración var, para el compilador es como si la var misma ocurriera en la parte superior
  • se combinan varias declaraciones var dentro del mismo alcance

Entonces este código:

var i = 1;
function abc() {
  i = 2;
  var i = 3;
}
console.log(i);     // outputs 1

es equivalente a:

var i = 1;
function abc() {
  var i;     // var declaration moved to the top of the scope
  i = 2;
  i = 3;     // the assignment stays where it is
}
console.log(i);

Esto puede parecer contrario a la intuición, pero tiene sentido desde la perspectiva de un diseñador de lenguaje imperativo.

jackbean818
fuente
5

Modern Js, ES6 +, ' const' y ' let'

Debería usar el alcance de bloque para cada variable que cree, al igual que la mayoría de los otros idiomas principales. varEs obsoleto . Esto hace que su código sea más seguro y más fácil de mantener.

constdebe usarse para el 95% de los casos . Hace que la referencia variable no pueda cambiar. Las propiedades de matriz, objeto y nodo DOM pueden cambiar y probablemente deberían serlo const.

letdebe usarse para cualquier variable que espera ser reasignada. Esto incluye dentro de un bucle for. Si alguna vez cambia el valor más allá de la inicialización, uselet .

El alcance del bloque significa que la variable solo estará disponible entre los corchetes en los que se declara. Esto se extiende a los ámbitos internos, incluidas las funciones anónimas creadas dentro de su ámbito.

Gibolt
fuente
3

Prueba este curioso ejemplo. En el ejemplo a continuación, si a fuera un valor numérico inicializado en 0, vería 0 y luego 1. Excepto que a es un objeto y javascript le pasará a f1 un puntero de en lugar de una copia del mismo. El resultado es que obtienes la misma alerta las dos veces.

var a = new Date();
function f1(b)
{
    b.setDate(b.getDate()+1);
    alert(b.getDate());
}
f1(a);
alert(a.getDate());
Mig82
fuente
3

Solo hay ámbitos de función en JS. ¡No bloquear ámbitos! También puedes ver lo que está levantando.

var global_variable = "global_variable";
var hoisting_variable = "global_hoist";

// Global variables printed
console.log("global_scope: - global_variable: " + global_variable);
console.log("global_scope: - hoisting_variable: " + hoisting_variable);

if (true) {
    // The variable block will be global, on true condition.
    var block = "block";
}
console.log("global_scope: - block: " + block);

function local_function() {
    var local_variable = "local_variable";
    console.log("local_scope: - local_variable: " + local_variable);
    console.log("local_scope: - global_variable: " + global_variable);
    console.log("local_scope: - block: " + block);
    // The hoisting_variable is undefined at the moment.
    console.log("local_scope: - hoisting_variable: " + hoisting_variable);

    var hoisting_variable = "local_hoist";
    // The hoisting_variable is now set as a local one.
    console.log("local_scope: - hoisting_variable: " + hoisting_variable);
}

local_function();

// No variable in a separate function is visible into the global scope.
console.log("global_scope: - local_variable: " + local_variable);
koredalin
fuente
(mucho tiempo desde la respuesta publicada) Alcance del bloque; developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
Bob
2

Tengo entendido que hay 3 ámbitos: alcance global, disponible a nivel mundial; alcance local, disponible para una función completa independientemente de los bloques; y alcance del bloque, solo disponible para el bloque, la declaración o la expresión en la que se utilizó. El alcance global y local se indica con la palabra clave 'var', ya sea dentro de una función o fuera, y el alcance del bloque se indica con la palabra clave 'let'.

Para aquellos que creen que solo hay un alcance global y local, explique por qué Mozilla tendría una página completa que describe los matices del alcance del bloque en JS.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let

mrmaclean89
fuente
2

Un problema muy común que aún no se describe y que los codificadores de front-end suelen encontrar es el alcance que es visible para un controlador de eventos en línea en el HTML, por ejemplo, con

<button onclick="foo()"></button>

El alcance de las variables a las que on*puede hacer referencia un atributo debe ser:

  • global (los controladores en línea de trabajo casi siempre hacen referencia a variables globales)
  • una propiedad del documento (por ejemplo, querySelectorcomo señalará una variable independiente document.querySelector; raro)
  • una propiedad del elemento al que está asociado el controlador (como arriba; raro)

De lo contrario, obtendrá un ReferenceError cuando se invoque el controlador. Entonces, por ejemplo, si el controlador en línea hace referencia a una función que está definida en el interior window.onload o $(function() {, la referencia fallará, porque el controlador en línea solo puede hacer referencia a variables en el ámbito global, y la función no es global:

Propiedades de la documenty propiedades del elemento el controlador está unido a también puede ser referenciado como variables independientes dentro de los manipuladores en línea porque los manipuladores de línea se invocan dentro de dos withbloques , uno para el document, uno para el elemento. La cadena de variables de alcance dentro de estos manejadores es extremadamente poco intuitiva , y un manejador de eventos de trabajo probablemente requerirá que una función sea global (y probablemente se debe evitar la contaminación global innecesaria ).

Dado que la cadena de alcance dentro de los controladores en línea es muy extraña , y dado que los controladores en línea requieren contaminación global para funcionar, y dado que los controladores en línea a veces requieren un escape de cadena feo al pasar argumentos, probablemente sea más fácil evitarlos. En su lugar, adjunte controladores de eventos usando Javascript (como con addEventListener), en lugar de con marcado HTML.


En una nota diferente, a diferencia de las <script>etiquetas normales , que se ejecutan en el nivel superior, el código dentro de los módulos ES6 se ejecuta en su propio ámbito privado. Una variable definida en la parte superior de una <script>etiqueta normal es global, por lo que puede hacer referencia a ella en otras <script>etiquetas, como esta:

Pero el nivel superior de un módulo ES6 no es global. Una variable declarada en la parte superior de un módulo ES6 solo será visible dentro de ese módulo, a menos que la variable se edite explícitamente exporto que se asigne a una propiedad del objeto global.

El nivel superior de un módulo ES6 es similar al del interior de un IIFE en el nivel superior de forma normal <script>. El módulo puede hacer referencia a cualquier variable que sea global, y nada puede hacer referencia a nada dentro del módulo a menos que el módulo esté específicamente diseñado para él.

Cierto rendimiento
fuente
1

En JavaScript hay dos tipos de alcance:

  • Alcance local
  • Alcance global

La siguiente función tiene una variable de alcance local carName. Y esta variable no es accesible desde fuera de la función.

function myFunction() {
    var carName = "Volvo";
    alert(carName);
    // code here can use carName
}

La clase de abajo tiene una variable de alcance global carName. Y esta variable es accesible desde cualquier lugar de la clase.

class {

    var carName = " Volvo";

    // code here can use carName

    function myFunction() {
        alert(carName);
        // code here can use carName 
    }
}
Abdur Rahman
fuente
1

ES5 y anterior:

Las variables en Javascript fueron inicialmente (pre ES6) léxicamente alcanzadas. El término de ámbito léxico significa que puede ver el alcance de las variables 'mirando' el código.

Cada variable declarada con la varpalabra clave tiene un alcance de la función. Sin embargo, si se declaran otras funciones dentro de esa función, esas funciones tendrán acceso a las variables de las funciones externas. Esto se llama una cadena de alcance . Funciona de la siguiente manera:

  1. Cuando una función busca resolver un valor variable, primero mira su propio alcance. Este es el cuerpo de la función, es decir, todo entre llaves {} (excepto las variables dentro de otras funciones que están en este ámbito).
  2. Si no puede encontrar la variable dentro del cuerpo de la función , subirá a la cadena y observará el alcance de la variable en la función en la que se definió la función . Esto es lo que se entiende con alcance léxico, podemos ver en el código dónde se definió esta función y, por lo tanto, podemos determinar la cadena del alcance simplemente mirando el código.

Ejemplo:

// global scope
var foo = 'global';
var bar = 'global';
var foobar = 'global';

function outerFunc () {
 // outerFunc scope
 var foo = 'outerFunc';
 var foobar = 'outerFunc';
 innerFunc();
 
 function innerFunc(){
 // innerFunc scope
  var foo = 'innerFunc';
  console.log(foo);
  console.log(bar);
  console.log(foobar);
  }
}

outerFunc();

¿Qué pasa cuando estamos tratando de registrar las variables foo, bary foobarque la consola es la siguiente:

  1. Intentamos iniciar sesión en la consola, se puede encontrar dentro de la innerFuncpropia función . Por lo tanto, el valor de foo se resuelve en la cadena innerFunc.
  2. Intentamos registrar la barra en la consola, la barra no se puede encontrar dentro de la función innerFuncmisma. Por lo tanto, necesitamos escalar la cadena de alcance . Primero observamos la función externa en la que innerFuncse definió la función . Esta es la función outerFunc. En el alcance de outerFuncpodemos encontrar la barra variable, que contiene la cadena 'outsideFunc'.
  3. foobar no se puede encontrar en innerFunc. . Por lo tanto, necesitamos escalar la cadena de alcance al alcance innerFunc. Tampoco se puede encontrar aquí, subimos otro nivel al alcance global (es decir, el alcance más externo). Aquí encontramos la variable foobar que contiene la cadena 'global'. Si no hubiera encontrado la variable después de subir la cadena de alcance, el motor JS arrojaría un error de referencia .

ES6 (ES 2015) y mayores:

Los mismos conceptos de ámbito léxico y ámbito de aplicación todavía se aplican ES6. Sin embargo, se introdujeron nuevas formas de declarar variables. Existen los siguientes:

  • let: crea una variable de ámbito de bloque
  • const: crea una variable de ámbito de bloque que debe inicializarse y no puede reasignarse

La mayor diferencia entre vary let/ constes que vartiene un alcance de función mientras que let/ consttiene un alcance de bloque. Aquí hay un ejemplo para ilustrar esto:

let letVar = 'global';
var varVar = 'global';

function foo () {
  
  if (true) {
    // this variable declared with let is scoped to the if block, block scoped
    let letVar = 5;
    // this variable declared with let is scoped to the function block, function scoped
    var varVar = 10;
  }
  
  console.log(letVar);
  console.log(varVar);
}


foo();

En el ejemplo anterior, letVar registra el valor global porque las variables declaradas con letámbito de bloque. Dejan de existir fuera de su bloque respectivo, por lo que no se puede acceder a la variable fuera del bloque if.

Willem van der Veen
fuente
0

En EcmaScript5, existen principalmente dos ámbitos, ámbito local y ámbito global, pero en EcmaScript6 tenemos principalmente tres ámbitos, ámbito local, ámbito global y un nuevo ámbito denominado ámbito de bloque .

Ejemplo de alcance de bloque es: -

for ( let i = 0; i < 10; i++)
{
 statement1...
statement2...// inside this scope we can access the value of i, if we want to access the value of i outside for loop it will give undefined.
}
Vivek Mehta
fuente
0

ECMAScript 6 introdujo las palabras clave let y const. Estas palabras clave se pueden usar en lugar de la palabra clave var. Contrariamente a la palabra clave var, las palabras clave let y const admiten la declaración de alcance local dentro de las declaraciones de bloque.

var x = 10
let y = 10
const z = 10
{
  x = 20
  let y = 20
  const z = 20
  {
    x = 30
    // x is in the global scope because of the 'var' keyword
    let y = 30
    // y is in the local scope because of the 'let' keyword
    const z = 30
    // z is in the local scope because of the 'const' keyword
    console.log(x) // 30
    console.log(y) // 30
    console.log(z) // 30
  }
  console.log(x) // 30
  console.log(y) // 20
  console.log(z) // 20
}

console.log(x) // 30
console.log(y) // 10
console.log(z) // 10
Davaakhuu Erdenekhuu
fuente
0

Realmente me gusta la respuesta aceptada pero quiero agregar esto:

Scope recopila y mantiene una lista de búsqueda de todos los identificadores (variables) declarados, y aplica un conjunto estricto de reglas sobre cómo son accesibles para el código que se está ejecutando actualmente.

El alcance es un conjunto de reglas para buscar variables por su nombre de identificador.

  • Si no se puede encontrar una variable en el alcance inmediato, Engine consulta el siguiente alcance externo, continuando hasta que se encuentre o hasta que se alcance el alcance más externo (también conocido como global).
  • Es el conjunto de reglas que determina dónde y cómo se puede buscar una variable (identificador). Esta búsqueda puede ser con el propósito de asignar a la variable, que es una referencia de LHS (lado izquierdo), o puede ser con el propósito de recuperar su valor, que es una referencia de RHS (lado derecho) .
  • Las referencias de LHS resultan de operaciones de asignación. Las asignaciones relacionadas con el alcance pueden ocurrir con el operador = o pasando argumentos a (asignar a) parámetros de función.
  • El motor de JavaScript primero compila el código antes de ejecutarlo, y al hacerlo, divide declaraciones como var a = 2; en dos pasos separados: 1er. Primero, var a para declararlo en ese ámbito. Esto se realiza al principio, antes de la ejecución del código. 2do. Más tarde, a = 2 para buscar la variable (referencia LHS) y asignarla si se encuentra.
  • Las búsquedas de referencia de LHS y RHS comienzan en el alcance que se está ejecutando actualmente y, si es necesario (es decir, no encuentran lo que están buscando allí), avanzan hacia arriba en el alcance anidado, un alcance (piso ) a la vez, buscando el identificador, hasta que llegan al global (piso superior) y se detienen, y lo encuentran o no lo hacen. Las referencias de RHS no cumplidas provocan que se arroje ReferenceError. Las referencias de LHS no cumplidas dan como resultado un global automático, creado implícitamente de ese nombre (si no está en modo estricto), o un error de referencia (si está en modo estricto).
  • El alcance consiste en una serie de "burbujas" que actúan cada una como un contenedor o cubo, en el que se declaran identificadores (variables, funciones). Estas burbujas se anidan cuidadosamente una dentro de la otra, y esta anidación se define en el momento del autor.
Ahmed KhaShaba
fuente
-3

Hay dos tipos de ámbitos en JavaScript.

  1. Alcance global : la variable que se anuncia en el alcance global se puede usar en cualquier parte del programa sin problemas. Por ejemplo:

    var carName = " BMW";
    
    // code here can use carName
    
    function myFunction() {
         // code here can use carName 
    }
  2. Ámbito funcional o ámbito local : la variable declarada en este ámbito solo se puede utilizar en su propia función. Por ejemplo:

    // code here can not use carName
    function myFunction() {
       var carName = "BMW";
       // code here can use carName
    }
A. Randhawa
fuente