Escuela Frontend
JavaScript

Descubre la Importancia de Aprender Sobre los Ámbitos de JavaScript

Horacio Herrera
Autor
Horacio Herrera

Si el título de este post te ha llamado la atención, puedo asumir que estás familiarizado con conceptos básicos de programación como crear variables y funciones. Conceptos básicos pero super importantes! 👏🏼

Ahora bien, alguna vez te has preguntado ¿Cómo JavaScript organiza y gestiona estas declaraciones? ¿Cómo sabe donde encontrar todo?

Esto es lo que aprenderás en este artículo.

¿Por qué debes aprender sobre los ámbitos de JavaScript?

Te soy sincero, aprender sobre los ámbitos no te va a hacer programar mejor, lo que va a hacer es ayudarte a entender mejor el lenguaje y lo más fundamental, ayudarte a solucionar problemas en tus programas para que puedas descifrar mejor y más rápido los errores que te puedas encontrar.

¡Yo lo veo como una ventaja bastante grande!

Compilado vs Interpretado

Si no estás familiarizado con la diferencia te doy una breve explicación:

Un lenguaje Interpretado, si en la línea 7 tiene un error, las líneas de la 1 a la 6 se ejecutaran independientemente del error en la línea 7.

En un lenguaje compilado hay generalmente tres fases: Lexing o Tokenizado, parseo y generación de código. Si existe un error en la fase de tokenizacion o parseo, el programa no ejecutara ninguna línea de código.  Así que cuando te encuentres un error de tipo ReferenceError, te acordaras que JavaScript ha lanzado un error de compilación.

Actualmente, un lenguaje de programación como JavaScript no se puede considerar como Compilado o Interpretado, ya que los navegadores modernos utilizan ambos procesos para poder optimizar la ejecución del mismo. ¡Gracias a esta conversación que tuve con Matías y Brendan pude aclararlo! (Gracias a ambos!)

A diferencia de un lenguaje compilado tradicional, el proceso de lexing y parsing en JavaScript pasa usualmente justo antes de la ejecución del mismo. Esto quiere decir que muchos trucos para optimizar la compilación de código no son posibles de implementar ni de sacarles provecho.

¿Qué son los Ámbitos en JavaScript?

Es la forma en la que JS sabe que variables son accesibles para cada expresión del programa, y la gestión de las mismas. En otras palabras, un ámbito o "scope" es cualquier sección de código que está delimitada por dos corchetes {}siempre y cuando haya alguna asignación a alguna variable dentro. Los ámbitos en un programa son creados basados en lo que el autor del código ha decidido, es decir que el autor es quien define los ámbitos que tendrá el programa cuando los escribe.

La fase en la que se generan los ámbitos es en él parseo o "parsing".  La mejor manera de entenderlos es intentar pensar como un compilador de JavaScript.

Veamos un ejemplo:

var a = 3
function hello(b) {
var a = 2
console.log(a, b)
}
hello(1) // 2, 1
  • Lo primero que observamos es var a = 3. Esto deriva en la creación de una variable llamada a en el ámbito global. La asignación del valor la hacemos en otro proceso.
  • Luego lo siguiente que vemos es function hello(b)..., Y esto deriva en lo mismo, una variable de tipo función llamada hello en el ámbito global. Al ser una variable de tipo función, creamos un nuevo ámbito y pasamos a definir las variables dentro del mismo
  • Aquí hay una creación implícita de una variable, que es el parámetro definido en la función hello. Esta variable al ser un parámetro de hello, es generada en el nuevo ámbito de la función.
  • Dentro de hello observamos var a = 2, Esto resulta en la creación de una variable llamada a en el ámbito de hello

Las demás líneas de código ni asignan, ni definen variables.

Detengamos un momento la explicación general de los ámbitos para hablar de una serie de detalles importantes sobre lo que acaba de pasar.

Está claro que en este programa tenemos dos variables con el mismo valor. Como ambas variables están definidas en diferentes ámbitos, No existe ningún tipo de colisión cuando vamos a acceder a esas variables (ver el resultado de la ejecución de hello). El valor 3 nunca se verá en el resultado de la ejecución de hello porque la variable a que está dentro "oculta" el valor de a en el scope global. Esto se le conoce como shadowing.

Los diferentes procesos de parseo y ejecución de nuestros programas está muy relacionado con lo que describimos como Hoisting.

En realidad Hoisting no existe, es la manera en la que explicamos que var a = 2 se divide en var a (creación de la referencia) y a = 2 (asignación de valor a la referencia).

¡Aclaro que este proceso es uno de muchos procesos que actualmente pasan cuando se ejecuta nuestro código, está simplificado bastante para poder explicar los puntos más importantes y que el artículo no sea eterno!

Encontrando referencias o "look-up"

Una vez nuestro programa esté parseado, podemos estar seguros de que todos los ámbitos y referencias a variables están definidos en sus respectivos lugares. Ahora veamos cómo funciona la ejecución de nuestro programa por parte del motor o "engine" de JavaScript.

Partimos del mismo programa:

var a = 3
function hello(b) {
var a = 2
console.log(a, b)
}
hello(1) // 2, 1

Tipos de Referencias

En esta fase, podemos decir que todas declaraciones de funciones son "invisibles", así que lo único que verá el motor de JavaScript será lo siguiente:

var a = 3
hello(1) // 2, 1

Cuando ejecutamos var a = 3, lo que estamos pidiendo es una referencia de tipo target, ya que le vamos a asignar un valor. En cambio, cuando ejecutamos la línea hello(1), la referencia que estamos buscando es de tipo source. Podemos mencionar que la referencia a a se busca en el lado Izquierdo (Left-hand Side look-up) y hello(1) se busca del lado Derecho (Right-hand Side look-up).

Supongo que puedes deducir que se llaman asi por el lado en el que estén con respecto al símbolo =.

Errores de Búsquedas

Dependiendo del tipo de búsqueda que estemos haciendo, obtendremos diferentes errores en la ejecución del programa, ahora veremos dos tipos de errores: ReferenceError y TypeError

ReferenceError

Cuando obtenemos este tipo de error, es porque el motor de JavaScript no ha encontrado esa referencia en el ámbito actual, ni en ninguno de los ámbitos que encapsulan el ámbito actual o ámbitos padre. Esto Expone otro mecanismo importante sobre el funcionamiento de los ámbitos y búsqueda de referencias:

Si una referencia no se encuentra en el ámbito actual, la búsqueda continúa en el ámbito padre hasta llegar al ámbito global

Veamos un ejemplo:

"use strict"
var saludo = "Hola"
function saludar() {
console.log(saludo, nombre)
}
saludar()

Cuando ejecutamos la línea console.log(saludo, nombre) no encontraremos la referencia a saludo en el ámbito de la función saludar, con lo que pasamos al ámbito padre y seguimos buscando. El ámbito padre de saludar es el global, y la referencia a saludo sí que existe. En el caso de la referencia nombre, es de tipo source y no se encuentra en ninguno de los ámbitos del programa, así que recibiremos el error ReferenceError: nombre is not defined.

Recibiremos este error en nuestra aplicación porque estamos ejecutando el programa en modo estricto (gracias al pragma encontrado en la primera línea de nuestro programa use strict). Si ejecutamos el mismo programa si este pragma, no obtendríamos el error porque el ámbito global creara la referencia por ti. Cabe mencionar que es "muy raro" encontrarse con este error actualmente, ya que muchos de los sistemas que usamos asumen que JavaScript se ejecute en modo estricto, y los transpiladores actuales (babel, webpack, rollup) agregan el pragma por ti.

TypeError

Este error lo veremos cuando queremos hacer una acción sobre alguna referencia que es imposible de hacer, como por ejemplo ejecutar una variable de tipo string como función

var a = "Hola!"
a("Horacio") // TypeError!

Tanto ReferenceError como TypeError son errores muy comunes de ver al programar con JavaScript, saber por qué se originan te ayudará a identificarlos y resolverlos más rápido.

¡Lo siguiente que veremos será cómo afecta los ámbitos a las nuevas tipos de variables let y const, pero esto lo dejo para otro artículo!

Desafío 🔥

Revisa y analiza el siguiente programa y responde a las siguientes preguntas:

var profesora = "Leira"
function nuevaClase() {
var profesora = "Claudia"
console.log(`Bienvenida, ${profesora}`)
}
function preguntar() {
var pregunta = "Segura"
console.log(`${pregunta} ${profesora}?`)
}
nuevaClase()
preguntar()
  1. ¿Cuál es el resultado de la ejecución de nuevaClase?
  2. ¿Cuál es el resultado de la ejecución de preguntar?
  3. ¿Qué ocasiona la definición de var profesora = "Claudia" dentro de la función nuevaClase?
  4. ¿Qué error obtenemos al borrar la línea var pregunta = "Segura" de la función preguntar?
  5. Enumera cuántos ámbitos existen en este programa

Conclusiones

Aprender sobre los ámbitos en JavaScript nos ayuda a "pensar" como los sistemas que ejecutan nuestro código, ayudándonos a entender mejor el proceso y encontrar soluciones a errores y problemas fácilmente y más rápido. Ser más conscientes de estos conceptos nos ayuda también a tomar mejores decisiones sobre cómo organizar y modelar nuestro código.

Otros Recursos

Artículos Relacionados

¿Quieres mejorar tus habilidades de frontend?