47

He tratado de explicar las clausuras en JavaScript a un amigo de 27 años pero he fallado completamente.

¿Cómo explicarle el concepto a alguien con los conocimientos que forman parte de las clausuras (como funciones y variables) pero que no entienden las clausuras por sí mismas?

Pregunta original por Zaheer Ahmed.

Arie Litovsky
  • 715
  • 5
  • 8
  • Hola Arie, gracias por tu aporte. El voto negativo (no es mío) probablemente se deba a que no todo el mundo está de acuerdo en traducir contenido directamente desde SO en inglés. Si te pasas por Meta verás un par de discusiones al respecto. – Konamiman Dec 07 '15 at 12:49
  • 2
    Estoy de acuerdo con @Konamiman pero también vale destacar que el concepto es de suficiente importancia como para tener una respuesta de calidad enteramente escrita (¡y pensada!) en español. Estas respuestas a conceptos base suelen funcionar como referencias adicionales y muchas veces terminan siendo más útiles que las oficiales. – Max Alcala Dec 07 '15 at 15:23
  • 3
    No comprendo porque esta pregunta fue marcada como una opinión/pregunta subjetiva. Quizás podría reescribirla como "¿cómo funcionan las clausuras?" en vez de "¿como tú explicarías una clausura?" – Arie Litovsky Dec 08 '15 at 16:24
  • 1
    O será que la opinión se trata de la discusión en meta sobre si deberíamos traducir preguntas de StackOverflow.com ? – Arie Litovsky Dec 08 '15 at 16:25
  • Tampoco lo entiendo yo. Quizás sea demasiado amplia, pero como funcionan las clausuras no me parece nada subjetivo. – tchrist Dec 09 '15 at 02:29
  • Aqui un buen enlace para entender con ejemplos:
    http://bonsaiden.github.io/JavaScript-Garden/es/#function.closures
    Disfruten programar!
    – fredyfx Dec 16 '15 at 16:39
  • 1
    asociación: https://stackoverflow.com/questions/111102/how-do-javascript-closures-work – JackNavaRow Jun 06 '19 at 19:39

8 Answers8

32

Lo complejo de explicar el concepto de una clausura es que requiere la comprensión de otros conceptos fundamentales como alcance léxico (lexical scope), variables libres (free variables) y los entornos de evaluación de una función. Asimismo es fundamental comprender la diferencia entre evaluar una función (la definición de la función misma) y evaluar una invocación de la misma (realizar una ejecución de la función).

Quizás es mejor tratar de entender la clausura como concepto, libre de los detalles de implementación de cualquier lenguaje.

Haré mi mejor intento de explicarlo como yo lo entiendo de la forma más sencilla posible.

Para entender las clausuras hay que pensar como un intérprete del lenguaje durante su proceso de evaluación de una invocación de una función.

Considera el siguiente ejemplo:

var nombre = "Lucas";
function saludar(saludo) {
    return saludo + ", " + nombre;
}

Pensando primero en el aspecto estático y declarativo de la función, en el ejemplo de arriba, la variable nombre es lo que se conoce como una variable libre, es decir, una variable que no es parte de los argumentos de la función, ni fue declarada dentro de su cuerpo. Aparte de esta variable, nuestra función también usa las variables declaradas como argumentos de la misma (por ejemplo: saludo).

Se espera por lo tanto que la variable nombre esté dentro del alcance léxico de nuestra función. Es decir, que haya sido declarada previamente a la declaración de nuestra función saludar (no dejes que el hoisting de JavaScript te engañe respecto a esto).

Dejemos ahora el tiempo de compilación de lado y veamos las cosas desde el punto de vista del tiempo de ejecución.

Si fuéramos un intérprete del lenguaje, al evaluar una invocación de nuestra función, por ejemplo saludar("Hola"), vamos a necesitar dos cosas:

  1. Un entorno conteniendo los valores de todas las variables en el alcance léxico de la función (bindings).

  2. El código de la función misma.

Eso es básicamente una clausura, la combinación de estas dos cosas.

Imagínate que siendo el intérprete de un lenguaje como JavaScript tuvieras acceso, a la hora de evaluar esta invocación en tiempo de ejecución, a una estructura hipotética como la siguiente, representando la clausura de nuestra invocación:

clausura_saludar = {
    entorno: {
        nombre: "Lucas",
        saludo: "Hola",
        saludar: ref funcion
    },
    funcion: function(saludo) {
        return saludo + ", " + nombre;
    }
}

Al evaluar la función, cuando el intérprete se topa con la variable saludo, la busca en el entorno de la clausura, cuando se topa con la variable nombre, la busca en el entorno de la clausura y al final puede evaluar la expresión saludo + ", " + nombre.

En otras palabras, el entorno es un diccionario que contiene cualquier nombre/identificador/ligadura usado dentro de la función misma (variables en uso de su alcance léxico).

Entonces una clausura es la función misma, más un entorno que contiene sus ligaduras (bindings) de las variables libres en el alcance léxico de la función, las ligaduras de los argumentos de la función, y a menudo un puntero o referencia a la función misma, en caso de que se trate de una función recursiva (en cuyo caso usará un identificador para representarse a sí misma y que por tanto requiere una ligadura en el entorno, que es lo que usa el intérprete para resolver cualquier nombre que necesite evaluar en el cuerpo de la función).

Como puedes ver, esto no requiere que la función de clausura se declare dentro de otra función. Simplemente se requiere que la misma tenga acceso a variables libres en su alcance léxico. Sin embargo, dadas las propiedades naturales del alcance léxico (por ejemplo: donde el ámbito más interno tiene acceso a las variables en todos los ámbitos ancestrales, pero no lo opuesto) es común usar las clausuras como una forma de encapsulamiento, como en el ejemplo dado en la otra respuesta a esta pregunta.

Edwin Dalorzo
  • 505
  • 3
  • 11
31

Las clausuras no son difíciles de entender.

Supone el siguiente código, el ejemplo mas simple que se me ocurre.

function saltos(valor) {
  var acumulado = 0;
  return function() {
    acumulado += valor; 
    return acumulado;   
  };
} 

var quintos = saltos(5);
quintos(); // retorna 5
quintos(); // retorna 10
quintos(); // retorna 15
// y asi sigue... 20, 25, 30..

Aquí estas creando una clausura. La creas en el momento que declaras una función dentro otra que usa variables de la funcion externa. Son las variables acumulado y valor las que quedan clausuradas en la funcion quintos. Y dicha "clausura" permanecerá asi mientras viva la función declarada internamente.

Para eliminar la clausura, basta con eliminar la referencia a la nueva funcion.

quintos = null;

En definitiva, incluso después de que termina la ejecución de saltos(5), JavaScript mantiene una referencia a las variables declaradas en saltos (acumulado y valor), visibles para la función creada en el interior.

Si te interesa conocer los detalles internos de como JavaScript mantiene estas referencias, hay algunas buenas respuestas que incluyen muchos detalles internos. Mi intencion fue responder la pregunta original, explicar clausuras usando terminos sencillos como variable y funcion.

alanfcm
  • 20,427
  • 11
  • 17
  • 34
rnrneverdies
  • 16,491
  • 3
  • 49
  • 79
15

Puesto que el OP asume que sabemos lo que es una función y variables, construyamos cosas con esos dos elementos:

Imaginemos que tenemos un programa en el que se ha declarado una función

function miFuncion (parametros) { ... }

Para simplificar mucho este escenario asumamos que esta función es una caja negra: no afecta a nada externo y se ve afectada por nada externo a su código y sus parámetros. Es la típica función predecible que siempre nos va a dar la misma salida para los mismos parámetros de entrada.

Pero ¿y si necesitamos que el resultado varíe? ¿Y si necesitamos, por ejemplo, que la función recuerde que ya ha sido llamada?

La manera más burda de solucionar este requerimiento sería crear una -horror- variable global. No vamos a entrar en por qué esta solución no es elegante ni recomendable, aceptemos que es una mala idea. Queremos algo más robusto, una variable que nadie más que esa función pueda tocar.

Aquí aparecen las clausuras de funciones. Este primer detalle es importante: el nombre completo es clausura, cierre o cerradura de función: vamos a tomar una función y encerrarla en un entorno propio que va a incluir a la función y a otras cosas. Este entorno será... otra función. Es decir, tendremos una función declarada dentro de otra. Esto significa que cada vez que llamemos a la función externa, se generará la función interna, al igual que las variables locales de una función normal:

function generarClausura () {
  let variableOculta = 1;
  return function funcionEncerrada(parametro) {
    console.log('Me han pasado el parametro',parametro,
          'y me han llamado',variableOculta,'veces');
    variableOculta++;
  }
}

Como puedes ver, la función interna funcionEncerrada es lo que devolvemos, por lo que su existencia no termina cuando la función externa se completa. Y, puesto que hace uso de una variable local de la función externa (en este caso variableOculta), ésta tampoco puede ser descartada. Hemos creado un entorno cerrado, solo visible para la función interna. Veámoslo en funcionamiento:

function generarClausura () {
  let variableOculta = 1;
  return function funcionEncerrada(parametro) {
    console.log('Me han pasado el parametro',parametro,
          'y me han llamado',variableOculta,'veces');
    variableOculta++;
  }
}


let funcionEncerrada= generarClausura();

funcionEncerrada('Primera llamada');
funcionEncerrada('Hola mundo');

Desde fuera es imposible acceder a la variableOculta porque fue declarada como variable local de la función generarClausura, pero para la funcionEncerrada se comporta como una variable global: está fuera de ella misma pero no ha desaparecido al finalizar la ejecución de ninguna de las dos funciones.

Para terminar, te pongo un ejemplo práctico pero general del uso de clausuras: Simular atributos privados en un objeto.

Primero veamos la construcción clásica para ver la limitación de Javascript: no existe el concepto de atributo privado

class MiClase {
  constructor(nombre) {
    this.atributo = nombre;
  }
  
  get nombre() {
    return this.atributo;
  }
}

let obj= new MiClase('Patito');
//usando el get
console.log(obj.nombre);
//accediendo directamente
obj.atributo = 'Pollito';
console.log(obj.nombre);

Ahora simulemos esta privacidad con clausuras:

function MiClase(nombre) {
  let atributoPrivado=nombre;
  this.getNombre = () => atributoPrivado;
  this.setNombre = nombre => atributoPrivado=nombre;
}

let obj= new MiClase('Patito');
console.log(obj.getNombre());

obj.atributoPrivado='Pollito';
console.log(obj.getNombre());
console.log(obj);

obj.setNombre('juan');
console.log(obj.getNombre());

Incluso aunque creemos un atributo en el objeto, este será ignorado porque realmente nuestros métodos set y get trabajan con la variable encerrada, que está fuera del alcance del código que no se haya escrito dentro de la función constructor.

Pablo Lozano
  • 45,934
  • 7
  • 48
  • 87
9

A ver ... por donde empezamos ... creo que por el principio.

Todos sabemos lo que es un programa, ¿ cierto ?

Programa = Código + Datos

Aun podemos afinar mas:

Programa = Funciones + Datos

Ok. ¿ Fácil, no es cierto ? Ahora, vamos a centrarnos en las funciones:

Función = Datos locales + Código

Fijémonos en que los datos locales no son parte de la función; son cosas externas a ella. Vale, hasta ahí es simple. Un poco mas de cerca ...

Función( argumentos ) {
  var variable1, variable2;

  instrucción 1;
  instrucción 2;
  instrucción 3;
}

Sigue siendo sencillo ... cada vez que se invoca a una función, se empieza a ejecutar por la primera instrucción, lo cúal tiene toda la lógica del mundo :-)

Pero en Javascript (y en muchos otros lenguajes, de hecho), es posible definir funciones dentro de funciones.

Función( argumentos ) {
  var variable1, variable2;

  instrucción 1;
  instrucción 2;
  instrucción 3;

  function subfunction1 ( ) {
    instrucción1-1;
    instrucción1-2;
    instrucción1-3;
  }
}

Vale. Ahora, vamos a hacer un pequeño cambio ...

Función( argumentos ) {
  var variable1, variable2;

  instrucción 1;
  instrucción 2;
  instrucción 3;
  return;

  instrucción1-1;
  instrucción1-2;
  instrucción1-3;
  return;
}

Simplemente, quitamos la función interna y la sustituimos por instrucciones ... colocando un return delante, para asegurarnos de que nunca lleguemos ahí. Fíjemonos que, en realidad, no se ha cambiado nada; a la función interna no podemos acceder desde fuera, y, desde dentro de la función, tampoco interfiere (hay un return justo delante).

Vale. Ahora ... ¿ que pasará si, de algún modo, pudieramos sacar fuera de esa función la dirección de instrucción1-1 ? ¿ Y si, además, fueramos capaces de entrar directamente a esa dirección ? Podemos usar un switch( ) para ello, cambiando muy poco las cosas:

Función( argumentos, entry ) {
  var variable1, variable2;

  if( typeof( entry ) != 'string' ) entry = 'begin';

  switch( entry ) {
  'begin':
    instrucción 1;
    instrucción 2;
    instrucción 3;
    break;

  'subfunction1':
    instrucción1-1;
    instrucción1-2;
    instrucción1-3;
    break;
  }

    return;
}

Ahora, podemos hacer

Funcción( 10 );

que ejecutará

Función( argumentos ) {
  var variable1, variable2;

  if( typeof( entry ) != 'string' ) entry = 'begin';

  switch( entry ) {
  'begin':
    instrucción 1;
    instrucción 2;
    instrucción 3;
    break;
  }
}

Pero también podríamos hacer

Función( argumentos, 'subfuncion1' );

que ejecutará

Función( argumentos, 'subfunction1' ) {
  var variable1, variable2;

  switch( entry ) {
  'subfunction1':
    instrucción1-1;
    instrucción1-2;
    instrucción1-3;
    break;
  }
}

Mediante ese sencillo cambio en nuestra perspectiva del concepto función, hemos conseguido no estar limitados a iniciar la ejecución siempre por el principio: podemos elegir que parte (que function interna) queremos ejecutar.

Pues ya tenemos una primera parte de lo que es una closure: un punto de entrada a una función, que no se corresponde con el punto de entrada normal de la misma.

¿ Y que pasa con variable1 y variable2 ?

Bueno, aquí es cuando la cosa se complica algo :-)

Hagamos una modificación sobre como se llama a una función:

function algo( value ) {
  var tmp = value;
  console.log( tmp );
}
...
algo( 10 );

En lugar de eso, imaginemos que, en realidad, se hace esto otro:

function algo( scope ) {
  if( scope.entry === undefined ) scope = 'begin';

  switch( scope.entry ) {
  'begin':
    scope.vars.tmp = scope.arguments[0];
    console.log( scope.vars.algo );
    break;

  'subfunction1':
    scope.vars.tmp = 1;
    break;

  'subfunction2':
    console.log( 'Se ha llamado a subfunction2( )' );
  };
}

y que la llamamos así:

algo( {
  vars: { tmp: undefined },
  arguments: [ 10 ],
  entry: undefined
} );

Eso es una closure:

Un objeto que contiene una serie de datos, entre ellos el punto de inicio de la ejecución.

Llegados a este punto, es posible iniciar la ejecución en uno cualquier de 3 puntos: begin, subfunction1 y subfunction2. Lo cual es equivalente a:

function algo( value ) {
  var tmp = 10;

  return subfunction1;

  function subfunction1( ) {
    tmp = 1;
  }

  function subfunction2( ) {
    console.log( 'Se ha llamado a subfunction2( )' );
  }
}

Y, además, mantemos el valor de las variables entre diferentes llamadas, puesto que dicho valor es externo al propio código de la función.

Al hacerlo así, se tienen una serie de propiedades:

  1. Pueden existir múltiples closures de una misma función; cada closure tiene su copia privada de datos y su propio punto de entrada.

  2. Se tratan como cualquier otro objeto: su tiempo de vida está determinado por su propio contador de referencias; cuando no hay referencias a la closure, esta se elimina.

  3. Al igual que cualquier otro objeto, no se eliminan mientras que tengan referencias ... por ejemplo, al ser usadas para callbacks.

Trauma
  • 25,297
  • 4
  • 37
  • 60
6

Para entender las clausuras voy a suponer que entiendes las características básicas del lenguaje que las hacen posibles:

  1. Se puede definir una función dentro de otra.
  2. Las variables accesibles desde una función son las que ésta define y las que definen las funciones que la engloban.
  3. Las funciones en javascript son como cualquier otro tipo de valor y se pueden devolver como retorno de otra función y asignarse a otra variable fuera de ambas.

Cuando una función devuelve otra función, la segunda mantiene las referencias a las variables que fueron definidas en la primera.

La clausura es esa capacidad de mantener las referencias, o englobarlas, o encerrarlas.

¡¡Sorpresa!! Al invocar a la función B desde el ámbito global, B puede acceder a variables de A que no son accesibles desde el ámbito global

¡¡Sorpresa!! Al invocar a la función B desde el ámbito global, B puede acceder a variables de A que no son accesibles desde el ámbito global.

Glantucan
  • 291
  • 2
  • 6
6

Aprovecho este espacio para ofrecer una versión traducida a nuestro idioma de una de las respuestas que refieres en tu pregunta, la cual tiene como base el excelente artículo de Morris sobre Closures titulado JavaScript Closures for Dummies.

No sé si esto pueda ser entendido por un niño de 6 años, pero puede ser al menos un comienzo para público de todas las edades :).

Dado que allí la respuesta ha sido marcada como Wiki de comunidad, respetamos la intención del autor, y la marcamos también aquí como Wiki.


Las clausuras no son mágicas

Esta página explica las clausuras para que un programador pueda entenderlas, utilizando el código JavaScript de trabajo. No es para gurús ni para programadores funcionales.

Las clausuras no son difíciles de entender una vez que el concepto de base se ha desarrollado. Sin embargo, ¡son imposibles de entender al leer cualquier artículo académico o información académica sobre ellas!

Este artículo está dirigido a programadores con cierta experiencia en programación en un lenguaje de flujo principal, y que pueden leer la siguiente función de JavaScript:

function sayHello(name) {
  var text = 'Hello ' + name;
  var say = function() { console.log(text); }
  say();
}
sayHello('Joe');

Dos resúmenes breves

  • Cuando una función (foo) declara otras funciones (bar y baz), la familia de variables locales creadas en foo no se destruye cuando la función sale. Las variables simplemente se vuelven invisibles para el mundo exterior. Por lo tanto, foo puede devolver astutamente las funciones bar y baz, y éstas pueden continuar leyendo, escribiendo y comunicándose entre sí a través de esta familia cerrada de variables ("la clausura") con la que nadie más puede entrometerse, ni siquiera alguien que llame a foo otra vez en el futuro.

  • Una clausura es una forma de apoyar funciones de primera clase; es una expresión que puede hacer referencia a variables dentro de su alcance (cuando se declaró por primera vez), asignarse a una variable, pasarse como argumento a una función o devolverse como resultado de una función.

Un ejemplo de clausura

El siguiente código devuelve una referencia a una función:

function sayHello2(name) {
  var text = 'Hello ' + name; //  variable local
  var say = function() { console.log(text); }
  return say;
}
var say2 = sayHello2('Bob');
say2(); // imprime "Hello Bob"

La mayoría de los programadores de JavaScript entenderán cómo una referencia a una función se devuelve a una variable (say2) en el código anterior. Si no lo hace, entonces necesita comprender eso antes de que pueda aprender acerca de las clausuras. Un programador que usa C pensaría que la función devolvía un puntero a una función, y que las variables say y say2 eran cada una un puntero a una función.

Hay una diferencia crítica entre un puntero C a una función y una referencia de JavaScript a una función. En JavaScript, se puede pensar que una variable de referencia de función tiene tanto un puntero a una función como un puntero oculto a una clausura.

El código anterior tiene una clausura porque la función anónima function() { console.log(text); } se declara dentro de otra función, sayHello2 () en este ejemplo. En JavaScript, si utilizas la palabra clave function dentro de otra función, estás creando una clausura.

En C y en la mayoría de los otros lenguajes comunes, después de que una función retorna, todas las variables locales ya no son accesibles porque se destruye el marco de pila.

En JavaScript, si declaras una función dentro de otra función, las variables locales de la función externa pueden permanecer accesibles después de que la función haya retornado. Esto se demuestró anteriormente, porque llamamos a la función say2 () después de haber retornado de sayHello2 (). Observa que el código que llamamos hace referencia a la variable text, que era una variable local de la función sayHello2 ().

function() { console.log(text); } // Salida de say2.toString();

Si observamos la salida de say2.toString (), podemos ver que el código se refiere a la variable text. La función anónima puede hacer referencia al texto que contiene el valor 'Hola Bob' porque las variables locales de sayHello2 () se han mantenido vivas secretamente en la clausura.

La clave es que en JavaScript una referencia de función también tiene una referencia secreta a la clausura en la que se creó, de manera similar a como los delegados son un puntero a un método más una referencia secreta a un objeto.

Más ejemplos

Por alguna razón, las clausuras parecen ser muy difíciles de entender cuando lees sobre ellas, pero cuando ves algunos ejemplos, queda claro cómo funcionan (me tomó un tiempo). Recomiendo trabajar con los ejemplos cuidadosamente hasta que entiendas cómo funcionan. Si comienzas a usar clausuras sin entender completamente cómo funcionan, ¡pronto crearías algunos errores muy extraños!

Ejemplo 3

Este ejemplo muestra que las variables locales no se copian, se mantienen por referencia. ¡Es como si el marco de pila se mantuviera vivo en la memoria incluso después de que existe la función externa!

function say667() {
  // Variable local que termina dentro de la clausura
  var num = 42;
  var say = function() { console.log(num); }
  num++;
  return say;
}
var sayNumber = say667();
sayNumber(); // escribe 43

Ejemplo 4

Las tres funciones globales tienen una referencia común al mismo cierre porque todas se declaran dentro de una sola llamada a setupSomeGlobals().

var gLogNumber, gIncreaseNumber, gSetNumber;
function setupSomeGlobals() {
  // Variable local que termina dentro de la clausura
  var num = 42;
  // Almacena algunas referencias a funciones como variables globales.
  gLogNumber = function() { console.log(num); }
  gIncreaseNumber = function() { num++; }
  gSetNumber = function(x) { num = x; }
}

setupSomeGlobals();
gIncreaseNumber();
gLogNumber(); // 43
gSetNumber(5);
gLogNumber(); // 5

var oldLog = gLogNumber;

setupSomeGlobals();
gLogNumber(); // 42

oldLog() // 5

Las tres funciones tienen acceso compartido a la misma clausura: las variables locales de setupSomeGlobals() cuando se definieron las tres funciones.

Ten en cuenta que en el ejemplo anterior, si llamas a setupSomeGlobals() nuevamente, se creará un nuevo cierre (stack-frame!). Las antiguas variables gLogNumber, gIncreaseNumber, gSetNumber se sobrescriben con nuevas funciones que tienen la nueva clausura. (En JavaScript, cada vez que declaras una función dentro de otra función, las funciones internas se recrean nuevamente cada vez que se llama a la función externa).

Ejemplo 5

Este ejemplo muestra que la clausura contiene variables locales que se declararon dentro de la función externa antes de salir. Ten en cuenta que la variable alice se declara realmente después de la función anónima. La función anónima se declara primero, y cuando se llama a esa función, puedes acceder a la variable alice porque alice está en el mismo ámbito (JavaScript hace la elevación de variables). También sayAlice()() simplemente llama directamente a la referencia de función devuelta por sayAlice(), es exactamente lo mismo que se hizo anteriormente pero sin la variable temporal.

function sayAlice() {
    var say = function() { console.log(alice); }
    // Variable local que termina dentro de la clausura.
    var alice = 'Hello Alice';
    return say;
}
sayAlice()();// Escribe "Hello Alice"

Difícil: también debes tener en cuenta que la variable say también está dentro de la clausura, y puede accederse a ella mediante cualquier otra función que pueda declararse dentro de sayAlice(), o puede accederse recursivamente dentro de la función que está dentro.

Ejemplo 6

Este es un verdadero problema para muchas personas, por lo que necesitas entenderlo. Ten mucho cuidado si estás definiendo una función dentro de un bucle: es posible que las variables locales de la clausura no actúen como podrías pensar.

Necesitas entender la característica de "elevación de variables" en Javascript para entender este ejemplo.

function buildList(list) {
    var result = [];
    for (var i = 0; i < list.length; i++) {
        var item = 'item' + i;
        result.push( function() {console.log(item + ' ' + list[i])} );
    }
    return result;
}

function testList() {
    var fnlist = buildList([1,2,3]);
    // Usando j solo para ayudar a prevenir la confusión -- se podría usar i.
    for (var j = 0; j < fnlist.length; j++) {
        fnlist[j]();
    }
}

 testList() //escribe "item2 undefined" 3 veces

La línea result.push (function () {console.log (item + '' + list [i])} agrega una referencia a una función anónima tres veces en la matriz de resultados. Si no estás tan familiarizado con las funciones anónimas, piense que es como:

pointer = function() {console.log(item + ' ' + list[i])};
result.push(pointer);

¡Ten en cuenta que cuando ejecutas el ejemplo, "item2 undefined" se registra tres veces! Esto se debe a que, al igual que en los ejemplos anteriores, solo hay una clausura para las variables locales para buildList (que son resultado, i y item). Cuando las funciones anónimas se llaman en la línea fnlist [j](); todos utilizan la misma clausura única, y usan el valor actual para i y para item dentro de esa clausura (donde i tiene un valor de 3 porque el bucle se había completado, y item tiene un valor de 'item2'). Ten en cuenta que estamos indexando desde 0, por lo tanto, item tiene un valor de item2. Y el i++ incrementará i al valor 3.

Puede ser útil ver qué sucede cuando se utiliza una declaración a nivel de bloque de la variable item (a través de la palabra clave let) en lugar de una declaración de variable con ámbito de función a través de la palabra clave var. Si se realiza ese cambio, entonces cada función anónima en el resultado de la matriz tiene su propia clausura. Cuando se ejecuta el ejemplo, la salida es la siguiente:

item0 undefined
item1 undefined
item2 undefined

Si la variable i también se define utilizando let en lugar de var, entonces la salida es:

item0 1
item1 2
item2 3

Ejemplo 7

En este ejemplo final, cada llamada a la función principal crea una clausura separada.

function newClosure(someNum, someRef) {
    // Variables locales que terminan dentro de la clausura.
    var num = someNum;
    var anArray = [1,2,3];
    var ref = someRef;
    return function(x) {
        num += x;
        anArray.push(num);
        console.log('num: ' + num +
            '; anArray: ' + anArray.toString() +
            '; ref.someVar: ' + ref.someVar + ';');
      }
}
obj = {someVar: 4};
fn1 = newClosure(4, obj);
fn2 = newClosure(5, obj);
fn1(1); // num: 5; anArray: 1,2,3,5; ref.someVar: 4;
fn2(1); // num: 6; anArray: 1,2,3,6; ref.someVar: 4;
obj.someVar++;
fn1(2); // num: 7; anArray: 1,2,3,5,7; ref.someVar: 5;
fn2(2); // num: 8; anArray: 1,2,3,6,8; ref.someVar: 5;

Sumario

Si todo parece completamente incierto, entonces lo mejor es jugar con los ejemplos. Leer una explicación es mucho más difícil de entender que los ejemplos. Mis explicaciones de clausuras y apilamientos, etc. no son técnicamente correctas, son simplificaciones generales que pretenden ayudar a comprender. Una vez que la idea básica ha sido asimilada, puedes recoger los detalles más adelante.

Puntos finales:

  • Cuando se utiliza function dentro de otra función, se utiliza una clausura.

  • Siempre que uses eval() dentro de una función, se usa una clausura. El texto que se evalúa puede hacer referencia a las variables locales de la función, y dentro de eval incluso puedes crear nuevas variables locales utilizando eval ('var foo = ...')

  • Cuando utilizas new function (...) (el constructor de funciones) dentro de una función, no creas una clausura. (La nueva función no puede hacer referencia a las variables locales de la función externa).

  • Una clausura en JavaScript es como mantener una copia de todas las variables locales, tal como eran cuando una función salía (exit).

  • Probablemente es mejor pensar que en una clausura siempre se crea solo una entrada a una función, y las variables locales se agregan a esa clausura.

  • Se mantiene un nuevo conjunto de variables locales cada vez que se llama a una función con una clausura (dado que la función contiene una declaración de función dentro de ella, y se devuelve una referencia a esa función interna o se mantiene una referencia externa de alguna manera).

  • Dos funciones pueden parecer que tienen el mismo texto de origen, pero tienen un comportamiento completamente diferente debido a su clausura "oculta". No creo que el código JavaScript pueda realmente averiguar si una referencia de función tiene una clausura o no.

  • Si estás intentando realizar modificaciones dinámicas al código fuente (por ejemplo: myFunction = Function(myFunction.toString().replace(/Hello/,'Hola'));), no funcionará si myFunction es una clausura ( por supuesto, nunca se le ocurriría hacer una sustitución de cadena de código fuente en tiempo de ejecución, pero ...).

  • Es posible obtener declaraciones de funciones dentro de las declaraciones de funciones dentro de las funciones y &mdash, y puedes obtener clausuras en más de un nivel.

  • Creo que normalmente una clausura es un término para la función junto con las variables que se capturan. ¡Ten en cuenta que no uso esa definición en este artículo!

  • Sospecho que las clausuras en JavaScript difieren de las que se encuentran normalmente en los lenguajes funcionales.

Enlaces

A. Cedano
  • 86,578
  • 19
  • 122
  • 221
4

La forma más simple de explicarlo es:

Las clausuras no son más que una función que vive dentro de otra función.

Es decir, imaginemos una función casa, al llamar a esta nos devolverá el número de la casa, pero dentro de la casa podemos tener personas y al llamar a esta casa por x persona nos responderá siempre alguien, pero aquí es la magia, no es posible llamar a una persona sin antes ir a su casa!

¿Cómo querer llamar a alguien si no se tiene su número telefónico o móvil? Pero podemos ir a la casa y luego llamar a la persona! De lo contrario, conseguir su número y luego llamar a esta!

function Casa() {
 // nos dice el numero de la casa
 console.log("hola llegaste a la casa #21");
 // ahora dentro de la casa una funcion Persona
 let Persona = function() {
  console.log(saludo);
 }
 // definimos el nombre e la persona
 let saludo = 'hola soy Bryro';
 // llamamos a la funcion Persona() y la almacenamos en la variable p;
 let p = Persona;
 // retornamos p
 return p;
}
//llamamos a la casa "traduccion leemos el letrero!"
Casa();
// ahora llamamos a la persona
Casa()();

Ahora bien, vamos a expandir el ejemplo preguntando por una persona:

function Casa() {
 // nos dice el numero de la casa
 console.log("hola llegaste a la casa #21");
 // ahora dentro de la casa una funcion Persona
 let Persona = function(nombre) {
  if(nombre =="Bryro"){
   console.log(`${saludo} ${nombre} en que puedo ayudarte?`);
  }else{
   console.log(`ups! ${nombre} no vive aqui!`); 
  }
 }
 // definimos el nombre e la persona
 let saludo = 'hola soy';
 // llamamos a la funcion Persona() y la almacenamos en la variable p;
 let p = Persona;
 // retornamos p
 return p;
}
//llamamos a la casa "traduccion leemos el letrero!"
Casa();
// ahora preguntamos por si vive Juan aqui!
Casa()("Juan");
// ahora preguntamos por Bryro 
Casa()("Bryro");
Bryro
  • 8,278
  • 1
  • 7
  • 24
3

Creamos máquinas

introducir la descripción de la imagen aquí

A veces explicar conceptos de informática se hace más asequible sí recurrimos al mundo físico.

El concepto de máquina en informática no es nuevo -Máquina de Turing- pero a veces pasamos por alto este importante detalle.

Decir que toda función es en realidad una máquina, es útil, porque una máquina para prestar sus servicios puede requerir de otras máquinas o también una máquina pueden crear otras máquinas.

Veamos una máquina sencilla y muy familiar: la calculadora

introducir la descripción de la imagen aquí

El diagrama de arriba puede ser expresado en javascript de la siguiente forma

function potencia(y){
  var exponente = y;
  // devuelve una funcion que requiere de una base
  // para que se pueda calcular la potencia
  return function (base){
    return Math.pow(base,exponente);
  };

};

var cubo = potencia(3);
var cuadrado = potencia(2);
var raiz = potencia(1/2);


console.log(cubo(4));      // se imprime 64
console.log(cuadrado(9));  // se imprime 81
console.log(raiz(25));     // se imprime 5


// Generalizando la máquina Potencia base(ba) 'x' al exponente(exp) 'y'
function pow(ba,exp){
  return potencia(exp)(ba); // el prime parentesis (exp) es para la
                            // primera function potencia
                            // el segundo parentesis (ba) corresponde
                            // a la funcion devuelta por la funcion
                            // potencia 
}

console.log(pow(2,4));    // se imprime 16

Finalmente quiero agregar que la base de estos conceptos provienen del algebra: f(x) = 2x, g(w, z) = wz+4, g(f(x), z) = 2xz + 4 pero a veces esto es explicado como una mera curiosidad del saber, desconectado de la realidad. Creo que este tipo de preguntas son importantes porque nos hacen revisar las bases sobre la cual se construyen las cosas.

Créditos de íconos: robot, teclado, portátil y ordenador:

Icons made by Dave Gandy from www.flaticon.com is licensed by CC 3.0 BY; Icons made by SimpleIcon from www.flaticon.com is licensed by CC 3.0 BY

; Icons made by Eucalyp from www.flaticon.com is licensed by CC 3.0 BY

HubertRonald
  • 3,079
  • 1
  • 8
  • 26
  • Esto no explica cómo funcionan las clausuras. Al parecer es algo de matemática. –  Feb 13 '19 at 17:34
  • Efectivamente @ArtEze, es un ejemplo que se basa en ellas no quise ahondar mucho en el código porque si vez `function potencia(y){... };` es lo mismo que @rnrneverdies explico en `function saltos(valor){... };` solo que me parecio apropiado dar un contexto general y mediante analogías para alguien que está iniciando en esto de las clasulas, relacionando conceptos que pueda tener previamente, aportando con ello otros puntos de vista diferentes a los ya expuestos. – HubertRonald Feb 13 '19 at 18:00
  • Se está hablando de clausuras, no conozco el término clasulas. ¿Está escrito mal o es lo mismo? –  Feb 13 '19 at 18:11
  • Gracias por la acotación @ArtEze – HubertRonald Feb 13 '19 at 18:13
  • Es que en ninguna parte de la respuesta se menciona a las clausuras. Así como está pienso que no es una respuesta a la pregunta, sino una respuesta a otra cosa. Si puedes agregar más detalles editando la respuesta creo que estaría bien. –  Feb 13 '19 at 18:20
  • Realmente esto es un ejemplo con un código que puede ser ejecutado, si vez `var cubo = potencia(3);` mantiene una copia de la variable local exponente, esto también ya fue mencionado por ejemplo por @A. Cedano. y corresponde a una clausura de javascript, por tanto no vi necesario repetir lo que ya otros usuarios han explicado de manera detalla antes que yo, sino dar un marco inicial que no vi plasmado en ninguno de los comentarios previos. – HubertRonald Feb 13 '19 at 18:39
  • [Continuemos el debate en el chat](https://chat.stackexchange.com/rooms/89685/discussion-between-arteze-and-hubert-ronald). –  Feb 13 '19 at 18:50