Command Palette

Search for a command to run...

Closures en JavaScript

Aprende qué son las closures, cómo funcionan, por qué son fundamentales en JavaScript, y cómo usarlas para crear funciones con estado privado, factories, y patrones avanzados.

Lectura: 14 min
Nivel: Intermedio

TL;DR - Resumen rápido

  • Closure: función que recuerda variables del ámbito donde fue creada
  • Las closures mantienen acceso a variables incluso después de que el ámbito externo termina
  • Basadas en el scope léxico (dónde se define, no dónde se ejecuta)
  • Fundamentales para encapsulación de estado y datos privados
  • Usadas en factories, módulos, y funciones de orden superior
  • Cuidado con memory leaks en closures con referencias circulares

¿Qué es una closure?

Una closure es una función que recuerda las variables del ámbito (scope) donde fue creada, incluso después de que ese ámbito haya terminado de ejecutarse. En JavaScript, las funciones mantienen una referencia a su entorno léxico, lo que les permite acceder a variables de funciones externas que ya no están en ejecución.

Las closures son posibles gracias al scope léxico de JavaScript. Cuando una función se define dentro de otra función, la función interna tiene acceso a las variables de la función externa. Esta relación persiste incluso después de que la función externa haya terminado de ejecutarse, creando una "cierre" sobre las variables del ámbito externo.

Scope léxico y closures

Las closures existen porque JavaScript usa scope léxico (static scoping). Las funciones acceden a las variables del ámbito donde fueron definidas, no del ámbito donde son ejecutadas. Esto permite que las closures "recuerden" su entorno original.

Cómo funcionan las closures

Cuando JavaScript crea una función, también crea un closure que contiene referencias a las variables del ámbito donde la función fue definida. Este closure es parte de la función y viaja con ella. Cuando la función se ejecuta, usa el closure para acceder a variables que no están en su propio scope local.

como-funcionan-closures.js
Loading code...

El ejemplo muestra cómo funciona una closure. La función crearContador define una variable contador y retorna una función que incrementa y retorna esa variable. Aunque crearContador termina de ejecutarse, la función retornada mantiene acceso a contador a través de la closure.

El proceso funciona así: la closure se crea cuando la función interna es definida, manteniendo referencias a variables del ámbito externo. Esta closure persiste después de que el ámbito externo termina su ejecución, permitiendo que la función acceda a variables externas que técnicamente ya no existen en el call stack. Cada invocación de la función externa crea un nuevo closure con sus propias variables independientes, por lo que contador1 y contador2 mantienen estados completamente separados.

Cada invocación = Nuevo closure

Cada vez que invocas una función que crea closures, se crea un nuevo closure con sus propias variables. Esto es por qué contador1 y contador2 en el ejemplo mantienen estados independientes.

Closures básicas

Las closures más simples son funciones que acceden a variables de su ámbito externo. Este patrón es fundamental en JavaScript y se usa constantemente, a veces sin que nos demos cuenta. Entender las closures básicas es el primer paso para dominar este concepto.

closures-basicas.js
Loading code...

El ejemplo muestra closures básicas en acción. La función saludar accede a la variable saludo del ámbito externo, creando una closure. Cada vez que invocamos saludar, usa la variable saludo a través de la closure, incluso aunque saludo no esté en su scope local.

Ubicuo en JavaScript

Las closures son ubicuas en JavaScript. Los event handlers, callbacks, y funciones de orden superior todas usan closures, a menudo implícitamente. Entender las closures te ayuda a escribir mejor código y debuggear más efectivamente.

Closures con parámetros

Las closures pueden capturar parámetros de funciones externas, no solo variables declaradas con var, let o const. Esto permite crear funciones personalizadas que "recuerdan" los valores de parámetros pasados en el momento de su creación.

closures-parametros.js
Loading code...

El ejemplo muestra closures que capturan parámetros. La función crearMultiplicador acepta un parámetro n y retorna una función que multiplica por ese valor. Cada closure creada "recuerda" el valor específico de n que se pasó cuando se invocó crearMultiplicador.

Los parámetros capturados se convierten en parte del closure, y cada closure recuerda su propio valor específico. Este patrón de function factory permite crear funciones personalizadas y configurar su comportamiento en el momento de la creación. Una característica importante es que los valores capturados no pueden ser modificados externamente, proporcionando inmutabilidad y encapsulación natural.

Function factories

El patrón de function factory usa closures con parámetros para crear funciones personalizadas. crearMultiplicador es un ejemplo: cada invocación crea una nueva función con comportamiento específico.

Closures en bucles

Las closures en bucles son un caso especial que a menudo causa confusión. Cuando creas closures dentro de un bucle, todas las closures pueden capturar la misma variable, causando que todas compartan el mismo valor final. Entender este comportamiento es esencial para evitar bugs sutiles.

closures-loops.js
Loading code...

El ejemplo muestra el problema clásico de closures en bucles. Cuando usamos var en el bucle, todas las closures capturan la misma variable i, que termina con el valor 5. Con let, cada iteración crea un nuevo i con su propio valor, o podemos usar IIFE para capturar el valor en cada iteración.

  • <strong>Problema con var:</strong> Todas las closures comparten la misma variable
  • <strong>Solución con let:</strong> Cada iteración tiene su propia variable
  • <strong>Solución con IIFE:</strong> Captura el valor en cada iteración
  • <strong>Scope de bloque:</strong> let crea scope de bloque en cada iteración
  • <strong>Valor capturado:</strong> Cada closure tiene su propio valor independiente

Cuidado con var en bucles

Evita usar var en bucles cuando crees closures. Prefiere let o usa IIFE para capturar el valor en cada iteración. Este es uno de los bugs más comunes relacionados con closures.

Casos de uso comunes

Las closures tienen numerosos casos de uso prácticos en JavaScript. Desde encapsulación de estado hasta function factories, las closures son una herramienta fundamental que permite patrones de diseño poderosos y código más expresivo.

casos-uso-closures.js
Loading code...

El ejemplo muestra varios casos de uso comunes de closures: encapsulación de estado privado con contadores, function factories para crear funciones personalizadas, y memoization para cachear resultados. Estos patrones demuestran la versatilidad y poder de las closures en JavaScript.

  1. <strong>Encapsulación:</strong> Crear estado privado en objetos y módulos
  2. <strong>Function factories:</strong> Crear funciones con comportamiento preconfigurado
  3. <strong>Event handlers:</strong> Mantener estado entre invocaciones de eventos
  4. <strong>Memoization:</strong> Cachear resultados de funciones costosas
  5. <strong>Currying:</strong> Transformar funciones de múltiples argumentos en funciones de un argumento
  6. <strong>Módulos:</strong> Simular módulos antes de ES6

Patrón Module

El patrón Module usa closures para crear estado privado y exponer solo métodos públicos. Este patrón era fundamental antes de ES6 para encapsulación y sigue siendo útil en situaciones donde no puedes usar módulos.

Closures y memoria

Las closures mantienen referencias a variables del ámbito externo, lo que puede afectar el manejo de memoria. Entender cómo las closures interactúan con el garbage collector es importante para evitar memory leaks, especialmente en aplicaciones de larga duración como aplicaciones web.

memoria-closures.js
Loading code...

El ejemplo muestra cómo las closures pueden causar memory leaks si no se manejan correctamente. La función crearClosureGrande crea una closure con un array grande. Mientras la closure exista, el array no puede ser liberado por el garbage collector. Es importante eliminar referencias a closures cuando ya no son necesarias.

Las closures mantienen referencias activas a variables externas, y el garbage collector no puede liberar esa memoria mientras las referencias existan. Closures con datos grandes pueden causar memory leaks significativos en aplicaciones de larga duración. Para mitigar esto, asigna null a las closures cuando ya no las necesites, y ten especial cuidado con referencias circulares que pueden impedir la liberación de memoria incluso cuando eliminas las referencias principales.

Memory leaks en event listeners

Los event listeners que usan closures son una fuente común de memory leaks. Si eliminas un elemento del DOM pero olvidas remover el event listener, la closure mantiene referencias al elemento, impidiendo que el garbage collector lo libere.

Resumen

Resumen: Closures en JavaScript

Conceptos principales:

  • Closure: función que recuerda variables del ámbito donde fue creada
  • Basadas en scope léxico: acceso a variables del ámbito de definición
  • Persisten después de que el ámbito externo termina
  • Cada invocación crea un nuevo closure independiente
  • Pueden capturar variables y parámetros del ámbito externo
  • Fundamentales para encapsulación y estado privado

Casos de uso:

  • Encapsulación de estado privado
  • Function factories para funciones personalizadas
  • Memoization para cachear resultados
  • Event handlers con estado persistente
  • Patrón Module para encapsulación
  • Currying y composición de funciones