Ámbito de aplicación en JavaScript

El alcance, o el conjunto de reglas que determinan dónde viven sus variables, es uno de los conceptos más básicos de cualquier lenguaje de programación. De hecho, es tan fundamental que es fácil olvidar lo sutiles que pueden ser las reglas.!

Comprender exactamente cómo el motor de JavaScript "piensa" sobre el alcance evitará que escriba los errores comunes que puede causar la elevación, lo preparará para envolver su cabeza alrededor de los cierres, y lo acercará mucho más a nunca escribir errores siempre otra vez.

... Bueno, te ayudará a entender la elevación y los cierres, de todos modos. 

En este artículo, echaremos un vistazo a:

  • Los fundamentos de los ámbitos en JavaScript.
  • Cómo el intérprete decide qué variables pertenecen a qué alcance.
  • cómo elevar De Verdad trabajos
  • cómo las palabras clave ES6 dejarconst cambia el juego

Vamos a bucear en.

Si está interesado en obtener más información sobre ES6 y cómo aprovechar la sintaxis y las funciones para mejorar y simplificar su código JavaScript, ¿por qué no echa un vistazo a estos dos cursos?

Alcance léxico

Si has escrito incluso una línea de JavaScript antes, sabrás que donde tu definir tus variables determinan donde puedes utilizar ellos. El hecho de que la visibilidad de una variable depende de la estructura de su código fuente se llama léxico alcance.

Hay tres formas de crear alcance en JavaScript:

  1. Crear una función. Las variables declaradas dentro de las funciones son visibles solo dentro de esa función, incluidas las funciones anidadas.
  2. Declarar variables con dejarconst dentro de un bloque de código. Dichas declaraciones solo son visibles dentro del bloque..
  3. Crear un captura bloquear. Lo creas o no, esta realidad hace crear un nuevo alcance!
"uso estricto"; var mr_global = "Mr Global"; function foo () var mrs_local = "Mrs Local"; console.log ("Puedo ver" + mr_global + "y" + mrs_local + "."); barra de funciones () console.log ("También puedo ver" + mr_global + "y" + mrs_local + ".");  foo (); // Funciona como se espera intente console.log ("Pero / I / can't see" + mrs_local + ".");  catch (err) console.log ("Acaba de obtener un" + err + ".");  deja foo = "foo"; barra constante = "barra"; console.log ("Puedo usar" + foo + bar + "en su bloque ...");  prueba console.log ("Pero no fuera de él");  catch (err) console.log ("Acaba de obtener otro" + err + ".");  // Lanza un error de referencia! console.log ("Tenga en cuenta que" + err + "no existe fuera de 'catch'!") 

El fragmento anterior muestra los tres mecanismos de alcance. Puedes ejecutarlo en Node o Firefox, pero Chrome no funciona bien con dejar, todavía.

Vamos a hablar de cada uno de estos con un detalle exquisito. Comencemos con una visión detallada de cómo JavaScript determina qué variables pertenecen a qué ámbito.

El proceso de compilación: una vista de pájaro

Cuando ejecutas un fragmento de JavaScript, suceden dos cosas para que funcione..

  1. Primero, tu fuente se compila.
  2. Luego, el código compilado se ejecuta..

Durante la Compilacion paso, el motor de JavaScript:

  1. toma nota de todos sus nombres de variables
  2. Los registra en el ámbito apropiado.
  3. Reserva espacio para sus valores.

Es solo durante ejecución que el motor de JavaScript realmente establece el valor de las referencias de variable igual a sus valores de asignación. Hasta entonces, son indefinido

Paso 1: Compilación

// Puedo usar first_name en cualquier parte de este programa var first_name = "Peleke"; función emergente (primer nombre) // solo puedo usar el último nombre dentro de esta función var last_name = "Sengstacke"; alerta (first_name + "+ last_name); ventana emergente (first_name);

Veamos lo que hace el compilador..

Primero, lee la línea. var first_name = "Peleke". A continuación, determina qué alcance Para guardar la variable en. Debido a que estamos en el nivel superior del script, se da cuenta de que estamos en el alcance global. Entonces, guarda la variable nombre de pila al alcance global e inicializa su valor para indefinido.

En segundo lugar, el compilador lee la línea con función emergente (primer nombre). Porque el función la palabra clave es lo primero en la línea, crea un nuevo ámbito para la función, registra la definición de la función en el ámbito global y se asoma dentro para encontrar declaraciones de variables.

Efectivamente, el compilador encuentra uno. Desde que tenemos var last_name = "Sengstacke" En la primera línea de nuestra función, el compilador guarda la variable. apellido al ámbito de aplicación de surgir-no al ámbito global y establece su valor en indefinido

Dado que no hay más declaraciones de variables dentro de la función, el compilador regresa al ámbito global. Y como no hay más declaraciones de variables. ahí, esta fase está hecha.

Tenga en cuenta que no tenemos en realidad correr nada aún. El trabajo del compilador en este punto es simplemente asegurarse de que sepa el nombre de todos; no le importa qué ellas hacen. 

En este punto, nuestro programa sabe que:

  1. Hay una variable llamada nombre de pila en el ámbito global.
  2. Hay una función llamada surgir en el ámbito global.
  3. Hay una variable llamada apellido en el ámbito de surgir.
  4. Los valores de ambos. nombre de pilaapellido son indefinido.

No importa que hayamos asignado esos valores de variables en otro lugar de nuestro código. El motor se encarga de eso en ejecución.

Paso 2: Ejecución

Durante el siguiente paso, el motor vuelve a leer nuestro código, pero esta vez, ejecuta eso. 

Primero, lee la línea., var first_name = "Peleke". Para ello, el motor busca la variable llamada. nombre de pila. Como el compilador ya ha registrado una variable con ese nombre, el motor la encuentra y establece su valor en "Peleke".

A continuación, lee la línea., función emergente (primer nombre). Ya que no estamos ejecutando La función aquí, el motor no está interesado y salta sobre ella..

Por último, lee la línea. emergente (primer_nombre). Desde que nosotros son ejecutando una función aquí, el motor:

  1. busca el valor de surgir
  2. busca el valor de nombre de pila
  3. ejecuta surgir como una función, pasando el valor de nombre de pila como parámetro

Cuando se ejecuta surgir, Pasa por este mismo proceso, pero esta vez dentro de la función. surgir. Eso:

  1. busca la variable llamada apellido
  2. conjuntos apellidovalor igual a "Sengstacke"
  3. mira hacia arriba alerta, ejecutándolo como una función con "Peleke Sengstacke" como su parámetro

Resulta que hay mucho más bajo el capó de lo que podríamos haber pensado!

Ahora que entiende cómo JavaScript lee y ejecuta el código que escribe, estamos listos para abordar algo un poco más cerca de casa: cómo funciona el levantamiento.

Elevación bajo el microscopio

Empecemos con un código.

bar(); barra de funciones () si (! foo) alerta (foo + "? Esto es extraño ...");  var foo = "barra";  roto (); // ¡Error de tecleado! var broken = function () alert ("¡Esta alerta no se mostrará!"); 

Si ejecuta este código, notará tres cosas:

  1. puede Referirse a foo antes de asignarle, pero su valor es indefinido.
  2. Tú puede llamada roto antes de definirlo, pero obtendrás un Error de tecleado.
  3. puede llamada bar Antes de definirlo, y funciona como se desea..

Levantamiento se refiere al hecho de que JavaScript hace que todos nuestros nombres de variables declarados estén disponibles en todos lados en sus ámbitos -incluyendo antes de les asignamos.

Los tres casos en el fragmento son los tres que deberá conocer en su propio código, por lo que los repasaremos uno por uno..

Levantando Declaraciones Variables

Recuerde, cuando el compilador de JavaScript lee una línea como var foo = "bar", eso:

  1. registra el nombre foo al alcance más cercano
  2. establece el valor de foo a indefinido

La razón por la que podemos usar foo antes de asignárnoslo es porque, cuando el motor busca la variable con ese nombre, hace existe. Por eso no lanza un Error de referencia

En su lugar, obtiene el valor indefinido, y trata de usar eso para hacer lo que le pidas. Por lo general, eso es un error.

Teniendo eso en cuenta, podríamos imaginar que lo que JavaScript ve en nuestra función bar es más como esto:

barra de funciones () var foo; // indefinido si (! foo) //! indefinido es verdadero, así que alerta (foo + "? Esto es extraño ...");  foo = "barra"; 

Este es el Primera regla de izamiento, si lo desea: las variables están disponibles a lo largo de su alcance, pero tiene el valor indefinido hasta que su código les asigne.

Un lenguaje común de JavaScript es escribir todos tus var Declaraciones en la parte superior de su alcance, en lugar de donde las use por primera vez. Parafraseando a Doug Crockford, esto ayuda a su código leer más como eso carreras.

Cuando lo piensas, eso tiene sentido. Está bastante claro por qué bar se comporta como lo hace cuando escribimos nuestro código como lo lee JavaScript, ¿no es así? Entonces, ¿por qué no escribir así? todos el tiempo?  

Expresiones de función de elevación

El hecho de que tengamos un Error de tecleado cuando intentamos ejecutar roto Antes de definirlo, es solo un caso especial de la primera regla.

Definimos una variable, llamada roto, que el compilador se registra en el ámbito global y establece igual a indefinido. Cuando intentamos ejecutarlo, el motor busca el valor de roto, encuentra que es indefinido, y trata de ejecutar indefinido como una función.

Obviamente, indefinido no es Una función es por eso que obtenemos una Error de tecleado!

Declaraciones de la función de elevación

Finalmente, recordemos que pudimos llamar bar antes lo definimos Esto se debe a la Segunda Regla del Levantamiento: Cuando el compilador de JavaScript encuentra una declaración de función, hace que su nombre y Definición disponible en la parte superior de su alcance. Reescribiendo nuestro código una vez más:

barra de funciones () si (! foo) alerta (foo + "? Esto es extraño ...");  var foo = "barra";  var quebrado; // barra indefinida (); // la barra ya está definida, ejecuta fine broken (); // ¡No se puede ejecutar indefinido! broken = function () alert ("¡Esta alerta no se mostrará!"); 

 Una vez más, tiene mucho más sentido cuando escribir como JavaScript lee, no piensas?

Para revisar:

  1. Los nombres de ambas declaraciones de variables y expresiones de funciones están disponibles en todo su ámbito, pero valores son indefinido hasta la asignación.
  2. Los nombres y Las definiciones de las declaraciones de funciones están disponibles en todo su ámbito., incluso antes sus definiciones.

Ahora echemos un vistazo a dos nuevas herramientas que funcionan de manera un poco diferente: dejarconst.

dejarconst, y la zona muerta temporal

diferente a var Declaraciones, variables declaradas con. dejar y const no hacer ser levantado por el compilador.

Al menos, no exactamente. 

Recuerda como pudimos llamar roto, pero consiguió un Error de tecleado porque tratamos de ejecutar indefinido? Si hubiéramos definido roto con dejar, habríamos conseguido un Error de referencia, en lugar:

"uso estricto"; // Tienes que "usar estricto" para probar esto en Node broken (); // ReferenceError! let broken = function () alert ("¡Esta alerta no se mostrará!"); 

Cuando el compilador de JavaScript registra variables en sus ámbitos en su primer paso, trata dejarconst diferente a como lo hace var

Cuando encuentra un var declaración, registramos el nombre de la variable a su alcance e inmediatamente inicializamos su valor para indefinido.

Con dejar, sin embargo, el compilador hace Registrar la variable a su alcance, pero noinicializar su valor para indefinido. En cambio, deja la variable sin inicializar., hasta El motor ejecuta su declaración de asignación. Accediendo al valor de una variable no inicializada se lanza un Error de referencia, lo que explica por qué el fragmento anterior se lanza cuando lo ejecutamos.

El espacio entre el principio de la parte superior del alcance de un dejar declaración y la declaración de asignación se llama el Zona muerta temporal. El nombre proviene del hecho de que, a pesar de que el motor sabe sobre una variable llamada foo en la parte superior del alcance de bar, La variable está "muerta", porque no tiene un valor..

… También porque matará tu programa si intentas usarlo temprano.

los const palabra clave funciona de la misma manera que dejar, con dos diferencias clave:

  1. Tú debe Asigne un valor cuando declare con const.
  2. Tú no poder reasignar valores a una variable declarada con const.

Esto garantiza que const será siempretiene el valor que inicialmente le asignó.

// Esto es legal const React = require ('reaccion'); // Esto no es totalmente legal const crypto; crypto = require ('crypto');

Alcance del bloque

dejarconst son diferentes de var De otra manera: el tamaño de sus alcances..

Cuando declara una variable con var, es visible tan alto en la cadena de alcance como sea posible, normalmente, en la parte superior de la declaración de la función más cercana, o en el alcance global, si lo declara en el nivel superior. 

Cuando declara una variable con dejarconst, sin embargo, es visible como en la zona como sea posible-solamente dentro del bloque mas cercano.

UNA bloquear es una sección de código dispuesta por llaves, como se ve con Si/más bloques, para los bucles, y en fragmentos de código explícitamente "bloqueados", como en este fragmento.

"uso estricto"; vamos foo = "foo"; if (foo) const bar = "bar"; var foobar = foo + bar; console.log ("Puedo ver" + bar + "en este bloque.");  prueba console.log ("Puedo ver" + foo + "en este bloque, pero no" + barra + ".");  catch (err) console.log ("Obtuvo un" + err + ".");  prueba console.log (foo + bar); // Se lanza debido a 'foo', pero ambos están indefinidos catch (err) console.log ("Acaba de obtener un" + err + ".");  console.log (foobar); // Funciona bien

Si declara una variable con constdejar dentro de un bloque, es solamente visible dentro del bloque, y solamente después de que lo hayas asignado.

Una variable declarada con var, sin embargo, es visible tan lejos como sea posible-En este caso, en el ámbito global..

Si te interesan los detalles esenciales de dejarconst, vea lo que el Dr. Rauschmayer tiene que decir sobre ellos en Explorando ES6: Variables y alcance, y eche un vistazo a la documentación de MDN sobre ellos.  

Léxico esta Y funciones de flecha

En la superficie, esta No parece tener mucho que ver con el alcance. Y, de hecho, JavaScript lo hace no resolver el significado de esta De acuerdo con las reglas de alcance que hemos hablado aquí..

Al menos, no por lo general. JavaScript, notoriamente, hace no resolver el significado de la esta palabra clave basada en donde la usaste:

var foo = nombre: 'Foo', idiomas: ['Español', 'Francés', 'Italiano'], habla: function speak () this.languages.forEach (function (language) console.log (this. nombre + "habla" + idioma + ".");); foo.speak ();

La mayoría de nosotros esperaríamos esta significar foo dentro de para cada bucle, porque eso es lo que significa justo fuera de ella. En otras palabras, esperaríamos que JavaScript resolviera el significado de esta léxicamente.

Pero no lo hace.

En su lugar, crea un nuevo esta Dentro de cada función que definas, y decide qué significa en función de cómo llamas a la función -no dónde lo definiste.

Ese primer punto es similar al caso de redefinición. alguna Variable en un ámbito hijo:

function foo () var bar = "bar"; function baz () // La reutilización de nombres de variables como esta se denomina "shadowing" var bar = "BAR"; console.log (bar); // BAR baz ();  foo (); // BAR

Reemplazar bar con esta, y todo debería aclararse al instante!

Tradicionalmente, conseguir esta para trabajar como esperamos que funcionen las antiguas variables de ámbito léxico, se requiere una de estas dos soluciones:

var foo = nombre: 'Foo', idiomas: ['Español', 'Francés', 'Italiano'], speak_self: function speak_s () var self = this; self.languages.forEach (function (language) console.log (self.name + "habla" + language + ".");), speak_bound: function speak_b () this.languages.forEach (function (language ) console.log (this.name + "habla" + language + "."); .bind (foo)); // Más comúnmente: .bind (este); ;

En hablar a sí mismo, salvamos el significado de esta a la variable yo, y use ese Variable para obtener la referencia que queremos. En speak_bound, usamos enlazar a permanentemente punto esta a un objeto dado.

ES2015 nos trae una nueva alternativa: funciones de flecha..

A diferencia de las funciones "normales", las funciones de flecha hacen no sombra su alcance de los padres esta valor mediante el establecimiento de su propia cuenta. Más bien, resuelven su significado. léxicamente. 

En otras palabras, si usas esta En una función de flecha, JavaScript busca su valor como lo haría con cualquier otra variable..

En primer lugar, comprueba el ámbito local para una esta valor. Como las funciones de flecha no configuran una, no la encontrará. A continuación, comprueba la padre alcance para un esta valor. Si encuentra uno, usará eso, en vez.

Esto nos permite reescribir el código anterior de esta manera:

var foo = nombre: 'Foo', idiomas: ['Spanish', 'French', 'Italian'], speak: function speak () this.languages.forEach ((language) => console.log (this .name + "habla" + language + "."););   

Si desea más detalles sobre las funciones de flecha, eche un vistazo al excelente curso de Dan Wellman, Instructor de Envato Tuts + sobre Fundamentos de JavaScript ES6, así como a la documentación de MDN sobre funciones de flecha.

Conclusión

Hemos cubierto mucho terreno hasta ahora! En este artículo, has aprendido que:

  • Las variables se registran a sus alcances durante Compilacion, y asociados con sus valores de asignación durante ejecución.
  • Referente a variables declaradas condejarconst antes de la asignación lanza un Error de referencia, y que tales variables estén orientadas al bloque más cercano..
  • Funciones de flechanos permite lograr la unión léxica de esta, y eludir el enlace dinámico tradicional.

También has visto las dos reglas de izar:

  • los Primera regla de izamiento: Que expresan funciones y var Las declaraciones están disponibles en todos los ámbitos en los que están definidos, pero tienen el valor indefinido hasta que sus instrucciones de asignación se ejecuten.
  • los Segunda Regla del Levantamiento: Que los nombres de las declaraciones de función. y Sus cuerpos están disponibles en todos los ámbitos donde están definidos..

Un buen próximo paso es utilizar su nuevo conocimiento de los ámbitos de JavaScript para envolver su cabeza en torno a los cierres. Para eso, echa un vistazo a los alcances y cierres de Kyle Simpson.

Por último, hay mucho más que decir sobre esta de lo que pude cubrir aquí. Si la palabra clave todavía parece mucha magia negra, eche un vistazo a esto y haga clic en Prototipos de objetos para entenderlo..

Mientras tanto, toma lo que has aprendido y escribe menos errores.!

Aprender JavaScript: la guía completa

Hemos creado una guía completa para ayudarlo a aprender JavaScript, ya sea que esté comenzando a trabajar como desarrollador web o si desea explorar temas más avanzados..