Cómo funcionan las clausuras en JavaScript: análisis con un ejemplo de Stack Overflow
\n\nSi alguna vez ha buscado en Google «JavaScript closures» o ha leído debates en Stack Overflow, seguramente habrá visto la famosa pregunta: «How do JavaScript closures work?». Este hilo ha acumulado miles de visitas y cientos de respuestas, desde explicaciones sencillas hasta una inmersión profunda en el motor V8. ¿Por qué? Porque las clausuras son uno de esos conceptos que al principio parecen magia y luego se convierten en su superarma.
\n\nEn este artículo no solo repetiremos la teoría. Nos meteremos bajo el capó de JavaScript, analizaremos qué sucede en la memoria y, por fin, entenderemos por qué este código funciona exactamente así y no de otra manera. ¿Listos? Allá vamos.
\n\n¿Qué es una clausura? Definición sin rodeos
\n\nEmpecemos con lo esencial. Una clausura (closure) es una función que «recuerda» las variables de su ámbito de visibilidad externo, incluso después de que esa función externa haya terminado su ejecución.
\n\n¿Suena a hechizo? Vayamos directamente al código. Aquí tiene un ejemplo clásico que encontrará en Stack Overflow:
\n\nfunction createCounter() {\n let count = 0;\n return function() {\n count++;\n return count;\n };\n}\n\nconst counter = createCounter();\nconsole.log(counter()); // 1\nconsole.log(counter()); // 2\nconsole.log(counter()); // 3\n\nAquí, createCounter devuelve una función anónima. Esta función interna «cierra» la variable count. Incluso después de que createCounter haya terminado (pensamos que todas las variables dentro de ella deberían desaparecer), count sigue viva y cambia con cada llamada a counter(). Eso es una clausura.
Bajo el capó: el entorno léxico
\n\nPara entender cómo funciona esto, debemos mirar dentro del motor de JavaScript. Cada función, al crearse, tiene una propiedad oculta [[Environment]]. Esta almacena una referencia al entorno léxico (Lexical Environment) en el que se creó la función.
El entorno léxico es simplemente un objeto que contiene:
\n- \n
- Un registro del entorno (Environment Record): todas las variables y funciones declaradas en ese ámbito de visibilidad. \n
- Una referencia al entorno externo (outer): el ámbito de visibilidad padre. \n
Cuando llamamos a una función, se crea un nuevo entorno léxico para esa llamada. La función interna accede a las variables de la función externa a través de la cadena de ámbitos (scope chain).
\n\nVolvamos a nuestro ejemplo. Cuando declaramos createCounter, se crea un entorno léxico global. Dentro de él están la variable createCounter (la función) y counter (aún undefined).
Cuando llamamos a createCounter():
- \n
- Se crea un nuevo entorno léxico para esta llamada. Dentro de él está la variable
count = 0. \n - Se crea una función anónima. Su
[[Environment]]apunta al entorno actual (donde vivecount). \n - La función se devuelve y se asigna a
counter. \n
Y aquí está lo más interesante: createCounter terminó, pero su entorno léxico no es eliminado por el recolector de basura. ¡Porque hay una referencia a él: el [[Environment]] de la función interna! El entorno «permanece» en la memoria mientras exista la función counter.
Ahora, cada vez que llamamos a counter(), se crea un nuevo entorno para esa llamada que, a través de la cadena, ve ese antiguo entorno con count. Así obtenemos el efecto de «recordar».
¿Por qué es importante? Escenarios reales
\n\nLas clausuras no son solo un concepto académico. Se utilizan en todas partes. Analicemos algunos ejemplos prácticos.
\n\n1. Variables privadas (encapsulación)
\n\nEn JavaScript no existen verdader