¿Cómo funcionan los Closures en Javascript Internamente?

Explora los closures en JavaScript y descubre su funcionamiento interno. Aprende cómo estas poderosas estructuras permiten el almacenamiento persistente de variables aún cuando pareciera que ya no existen. Después de leer este artículo entenderás cómo funcionan los closures en Javascript y cómo puedes utilizarlos en tus proyectos.

En este artículo aprenderás:

  1. ¿Qué significa 'closure' en Javascript (definición teórica)?
  2. ¿Cómo funciona un closure internamente?
  3. Regresar una función desde otra función
  4. Ejemplo - Crear una función que solamente es ejecutable 1 vez
  5. Resumen

¿Qué significa 'closure' en Javascript (definición teórica)?

"Una clausura o closure es una función que guarda referencias del estado adyacente (alcance léxico). En otras palabras, una clausura permite acceder al alcance de una función exterior desde una función interior. En JavaScript, las clausuras se crean cada vez que una función es creada." - Mozilla MDN

Y eso... ¿qué significa?

En otras palabras, un closure ocurre cuando una función interna (o anidada) tiene acceso a las variables de una función padre (o superior) aún cuando la función padre ya haya terminado de ejecutarse.

Ejemplo

function padre() {
  const variablePadre = "Hola, soy una variable de la función padre";
  function hijo() {
    console.log(variablePadre);
  }
  return hijo;
}
const funcionHijo = padre();
funcionHijo(); // "Hola, soy una variable de la función padre"

¿Cómo funciona un closure internamente?

Para entender mejor el ejemplo anterior y cómo funciona un closure internamente es necesario entender el concepto de Alcance (scope) en Javascript.

Alcance (Scope)

"El contexto actual de ejecución. El contexto en el que los valores y las expresiones son "visibles" o pueden ser referenciados. Si una variable u otra expresión no está "en el Scope- alcance actual", entonces no está disponible para su uso." - Mozilla MDN

En Javascript existen 3 tipos de alcance:
  1. Alcance global (Global Scope)
  2. Alcance local (Local Scope)
  3. Alcance léxico (Lexical Scope)

1. Alcance global (Global Scope)

El alcance global es el alcance que tiene una variable cuando es declarada fuera de una función. En otras palabras, una variable declarada fuera de una función es accesible desde cualquier parte del programa.

const variableGlobal = "Hola, soy una variable global";
function func() {
  console.log(variableGlobal);
}
func(); // "Hola, soy una variable global"
console.log(variableGlobal); // "Hola, soy una variable global"

2. Alcance local (Local Scope)

El alcance local se refiere al alcance de las variables que se declaran dentro de una función o bloque de código específico. Estas variables solo son accesibles dentro de la función o bloque donde están definidas y no son visibles fuera de ese alcance. Cuando se ejecuta la función o el bloque, se crea un nuevo alcance local y cualquier variable declarada dentro de él existe solo mientras la función o el bloque se esté ejecutando.

function func() {
  const variableLocal = "Hola, soy una variable local";
  console.log(variableLocal);
}
func(); // "Hola, soy una variable local"
console.log(variableLocal); // Uncaught ReferenceError: variableLocal is not defined

3. Alcance léxico (Lexical Scope)

El alcance léxico se refiere a la visibilidad y accesibilidad de las variables dentro de las funciones anidadas en una función. Cuando se define una función anidada, se guarda el alcance del código que la rodea dentro de la función 'padre'. El alcance léxico permite que las funciones internas accedan a las variables desde sus funciones externas, pero no al revés.

function funcPadre() {
  const variablePadre = "Hola, soy una variable de la función padre";
  function funcHijo() {
    console.log(variablePadre);
  }
  funcHijo(); // "Hola, soy una variable de la función padre"
  console.log(variablePadre); // Uncaught ReferenceError: variablePadre is not defined
}

Este último alcance es el que nos interesa para entender cómo funciona un closure internamente.

Regresar una función desde otra función

En JavaScript cuando regresamos una función a partir de otra función, la función que regresamos mantiene acceso a las variables de la función que la regresó (a esto se refiere el concepto de closure).

Lo que diferencía a estas variables de las variables almacenadas globalmente es que las variables almacenadas en un closure son privadas y no pueden ser accedidas desde fuera de la función que las regresó, solamente son accesibles por medio de la función que tiene acceso a ellas.

Una manera más sencilla de entenderlo es que cuando guardamos la referencia a una función en una variable, la variable que guarda la referencia a la función también guarda el alcance de la función (alcance léxico).

function padre() {
  const variablePadre = "Hola, soy una variable de la función padre";
  function hijo() {
    console.log(variablePadre);
  }
  return hijo;
}

const funcionHijo = padre(); // Almacenamos la referencia a la función hijo en la variable funcionHijo y también almacenamos el alcance de la función padre (es decir, la variable variablePadre).

En memoria, la variable funcionHijo guarda la referencia a la función hijo y también guarda el alcance de la función padre (es decir, la variable variablePadre).

Ejemplo - Crear una función que solamente es ejecutable 1 vez

En este ejemplo vamos a crear una función que solamente se pueda ejecutar 1 vez. Para lograr esto vamos a utilizar un closure.

function generador() {
  let contador = 0;
  function once() {
    if (contador === 0) {
      console.log("Hola, soy una función que solamente se ejecuta 1 vez");
      // Ejecutamos lógica de la función aquí
      contador++;
    } else {
      console.log("No se puede ejecutar esta función más de 1 vez");
    }
  }
  return once;
}
const funcionOnce = generador();
funcionOnce(); // "Hola, soy una función que solamente se ejecuta 1 vez"
funcionOnce(); // "No se puede ejecutar esta función más de 1 vez"
// La variable contador no existe en el alcance global, solo en el alcance léxico de funcionOnce.
console.log(contador); // Uncaught ReferenceError: contador is not defined.

¿Qué está pasando internamente?

  1. Declaramos la función generador.
  2. Dentro de la función generador declaramos una variable llamada contador y le asignamos el valor de 0.
  3. Dentro de la función generador declaramos la función once.
  4. Regresamos la referencia a la función once.
  5. Almacenamos la referencia a la función once en la variable funcionOnce. También almacenamos el alcance de la función generador (es decir, la variable contador) dentro de funcionOnce. funcionOnce ahora tiene acceso al código de la función once y tambien tiene acceso a la variable contador. Esta variable está disponible solamente mediante el alcance léxico.
  6. Ejecutamos la función funcionOnce.
  7. La variable contador tiene un valor de 0, por lo tanto, se ejecuta el código dentro del if y se imprime en consola el mensaje "Hola, soy una función que solamente se ejecuta 1 vez".
  8. La variable contador ahora tiene un valor de 1. Este valor se mantiene en la memoria de funcionOnce.
  9. Ejecutamos la función funcionOnce nuevamente.
  10. La variable contador tiene un valor de 1, por lo tanto, se ejecuta el código dentro del else y se imprime en consola el mensaje "No se puede ejecutar esta función más de 1 vez".

Resumen

  • Un closure ocurre cuando una función interna (o anidada) tiene acceso a las variables de una función padre (o superior) aún cuando la función padre ya haya terminado de ejecutarse.
  • Cuando regresamos una función a partir de otra función, la función que regresamos mantiene acceso a las variables de la función 'padre' (a esto se refiere el concepto de closure).

  • Cuando guardamos la referencia a una función en una variable, la variable que guarda la referencia a la función también guarda el alcance de la función.

Ayúdame a mejorar este artículo

¿Quisieras complementar este artículo o encontraste algún error?¡Excelente! Envíame un correo.

  • seb@sebastianfdz.com