Saltar al contenido principal
03Funciones

Closures (Clausuras)

Clase 7 de 77 min de lectura

¿Qué pasa con las variables de una función cuando esta termina? Lo lógico sería que desaparecieran. Pero a veces siguen ahí, y la explicación son los closures (clausuras), uno de los conceptos más poderosos y elegantes de JavaScript.

¿Qué son los Closures?#

Un closure (clausura) es cuando una función «recuerda» las variables de su scope padre, incluso después de que esa función padre haya terminado de ejecutarse.

Piensa en una mochila. Cuando una función se crea, se lleva a la espalda las variables que tenía a su alrededor, y las conserva vaya donde vaya.

En otras palabras, un closure te permite acceder al scope de una función externa desde una función interna. Y no hay que hacer nada especial: los closures se crean cada vez que se define una función.

Lo mejor es verlo con un ejemplo. Fíjate en que crearContador devuelve otra función, y en lo que pasa con cuenta:

function crearContador() {  let cuenta = 0 // Variable en el scope padre  return function () {    cuenta++ // La función interna "recuerda" cuenta    return cuenta  }}const contador = crearContador()console.log(contador()) // -> 1console.log(contador()) // -> 2console.log(contador()) // -> 3// ¡La variable 'cuenta' sigue existiendo!

¿Cómo funciona?

  1. crearContador() crea una variable cuenta, que empieza en 0.
  2. Devuelve una función que usa esa variable.
  3. crearContador() termina, pero la función devuelta mantiene el acceso a cuenta.
  4. Cada vez que llamamos a contador(), se modifica esa misma variable cuenta. Por eso el número sigue subiendo en lugar de volver a 1.

Diagrama de un Closure

Infografía
Un closure recuerda sus variables
7.Cada llamada a crearContador() crea una cuenta nueva: contador2 empieza en 1 y contador sigue en 3.
Paso 7 de 7

Ejemplos Prácticos de Closures#

1. Múltiples contadores independientes

¿Qué pasa si llamamos dos veces a crearContador? Sigue el orden de los resultados:

function crearContador() {  let cuenta = 0  return function () {    cuenta++    return cuenta  }}const contador1 = crearContador()const contador2 = crearContador()// contador1 y contador2 son independientes// y cada uno tiene su propia variable "cuenta"console.log(contador1()) // -> 1console.log(contador1()) // -> 2console.log(contador2()) // -> 1 (independiente)console.log(contador1()) // -> 3console.log(contador2()) // -> 2

Cada llamada a crearContador crea una variable cuenta nueva, así que cada contador tiene la suya propia y no se pisan. Esa es la magia de los closures.

2. También con parámetros

No solo pasa con las variables declaradas dentro de la función. La función interna también recuerda los parámetros de la función padre:

function crearSaludador(saludo) {  return function (nombre) {    return saludo + ', ' + nombre + '!'  }}const saludarEnEspanol = crearSaludador('Hola')const saludarEnIngles = crearSaludador('Hello')const saludarEnFrances = crearSaludador('Bonjour')console.log(saludarEnEspanol('Ana')) // -> "Hola, Ana!"console.log(saludarEnIngles('John')) // -> "Hello, John!"console.log(saludarEnFrances('Marie')) // -> "Bonjour, Marie!"

Tres saludadores distintos, y cada uno recuerda el saludo con el que se creó.

3. Datos privados con closures

Otra utilidad de los closures es crear datos privados: variables a las que no se puede acceder desde fuera de la función, pero sí desde dentro.

El truco es devolver un objeto con métodos: son la única puerta de entrada a esos datos.

function crearPersona(nombreInicial) {  let nombre = nombreInicial // Variable "privada"  let edad = 0  return {    getNombre: function () {      return nombre    },    setNombre: function (nuevoNombre) {      nombre = nuevoNombre    },    getEdad: function () {      return edad    },    cumplirAnos: function () {      edad++      return edad    }  }}const persona = crearPersona('Juan')console.log(persona.getNombre()) // -> "Juan"persona.setNombre('Carlos')console.log(persona.getNombre()) // -> "Carlos"console.log(persona.cumplirAnos()) // -> 1// No podemos acceder directamente a las variables privadasconsole.log(persona.nombre) // -> undefinedconsole.log(persona.edad) // -> undefined

nombre y edad no son propiedades del objeto: viven en el closure. Por eso persona.nombre es undefined.

Closures vs Variables Globales#

No hay que confundir los closures con las variables globales. Son conceptos muy distintos y, de hecho, los closures son una buena solución para evitar las variables globales y los problemas que traen.

El problema con las variables globales

Una variable global está al alcance de todo el código, también del que no debería tocarla:

// Variables globales - pueden ser modificadas por cualquieralet contador = 0function incrementar() {  contador++  return contador}function decrementar() {  contador--  return contador}// Problema: cualquier código puede modificar 'contador'contador = 1000 // ❌ ¡Ups! Se rompió nuestro contador

La solución con closures

Si metemos la variable dentro de una función y devolvemos solo los métodos para trabajar con ella, queda protegida:

// Closure - datos protegidosfunction crearContador() {  let contador = 0 // Variable privada  return {    incrementar: function () {      contador++      return contador    },    decrementar: function () {      contador--      return contador    },    obtenerValor: function () {      return contador    }  }}const miContador = crearContador()console.log(miContador.incrementar()) // -> 1console.log(miContador.incrementar()) // -> 2console.log(miContador.decrementar()) // -> 1// No podemos acceder directamente a 'contador'miContador.contador = 1000console.log(miContador.obtenerValor()) // ✅ 1, la variable privada sigue intacta

Ventajas de los Closures#

Beneficios

  • Encapsulación. Datos privados y protegidos de modificaciones externas.
  • Persistencia. Las variables mantienen su estado entre llamadas.
  • Flexibilidad. Funciones configurables y reutilizables, como crearSaludador.
  • Modularidad. Código más limpio y organizado.
  • Independencia. Cada closure mantiene su propio contexto.

Consideraciones

  • Memoria. Los closures mantienen referencias a sus variables, así que pueden consumir más memoria.
  • Complejidad. Pueden hacer el código más difícil de entender si estás empezando.

Los datos clave de las closures#

Si te quedas con una sola idea, que sea la de la mochila: una función se lleva consigo las variables del lugar donde nació.

En resumen

  • Un closure es una función que «recuerda» las variables de su scope padre.
  • Mantiene el acceso a esas variables incluso después de que la función padre haya terminado.
  • Cada llamada a la función padre crea un closure independiente, con sus propias variables.
  • Sirven para tener estado privado y funciones configurables sin recurrir a variables globales.