scope javascript que




¿Cómo funcionan los cierres de JavaScript? (20)

¿Cómo explicaría los cierres de JavaScript a alguien con un conocimiento de los conceptos en que consisten (por ejemplo, funciones, variables y similares), pero no entiende los cierres por sí mismos?

He visto el ejemplo del Esquema dado en Wikipedia, pero desafortunadamente no ayudó.

https://code.i-harness.com


Los niños siempre recordarán los secretos que han compartido con sus padres, incluso después de que estos se hayan ido. Esto es lo que son los cierres para funciones.

Los secretos para las funciones de JavaScript son las variables privadas.

var parent = function() {
 var name = "Mary"; // secret
}

Cada vez que lo llamas, la variable local "nombre" se crea y se le da el nombre "María". Y cada vez que la función sale, la variable se pierde y el nombre se olvida.

Como puede suponer, porque las variables se recrean cada vez que se llama a la función, y nadie más las conocerá, debe haber un lugar secreto donde se almacenan. Podría llamarse Cámara de los Secretos o pila o ámbito local, pero en realidad no importa. Sabemos que están ahí, en algún lugar, escondidos en la memoria.

Pero, en JavaScript hay una cosa muy especial que las funciones que se crean dentro de otras funciones, también pueden conocer las variables locales de sus padres y mantenerlas mientras vivan.

var parent = function() {
  var name = "Mary";
  var child = function(childName) {
    // I can also see that "name" is "Mary"
  }
}

Entonces, mientras estemos en la función principal, puede crear una o más funciones secundarias que compartan las variables secretas desde el lugar secreto.

Pero lo triste es que si el niño es también una variable privada de su función principal, también morirá cuando el padre termine, y los secretos morirán con ellos.

Para vivir, el niño tiene que irse antes de que sea demasiado tarde.

var parent = function() {
  var name = "Mary";
  var child = function(childName) {
    return "My name is " + childName  +", child of " + name; 
  }
  return child; // child leaves the parent ->
}
var child = parent(); // < - and here it is outside 

Y ahora, aunque Mary "ya no corre", su recuerdo no se pierde y su hija siempre recordará su nombre y otros secretos que compartieron durante su tiempo juntos.

Entonces, si llamas a la niña "Alice", ella responderá.

child("Alice") => "My name is Alice, child of Mary"

Eso es todo lo que hay que contar.


Cierres de JavaScript para principiantes

Enviado por Morris el martes, 2006-02-21 10:19. Editado por la comunidad desde.

Los cierres no son mágicos

Esta página explica los cierres para que un programador pueda entenderlos, utilizando el código JavaScript de trabajo. No es para gurús o programadores funcionales.

Los cierres no son difíciles de entender una vez que el concepto de núcleo se ha desarrollado. Sin embargo, ¡son imposibles de entender leyendo cualquier explicación teórica o académicamente orientada!

Este artículo está dirigido a programadores con cierta experiencia en programación en un lenguaje general 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 breves resúmenes

  • Cuando una función (foo) declara otras funciones (barra 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 pueden seguir leyendo, escribiendo y comunicándose entre sí a través de esta familia cerrada de variables ("el cierre") con la que nadie más puede entrometerse, ni siquiera alguien que llama. Foo otra vez en el futuro.

  • Un cierre 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 un cierre.

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

function sayHello2(name) {
  var text = 'Hello ' + name; // Local variable
  var say = function() { console.log(text); }
  return say;
}
var say2 = sayHello2('Bob');
say2(); // logs "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 (por say2 ) en el código anterior. Si no lo hace, entonces debe mirar eso antes de que pueda aprender acerca de los cierres. 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 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, puede pensar que una variable de referencia de función tiene tanto un puntero a una función como un puntero oculto a un cierre.

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

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

En JavaScript, si declara una función dentro de otra función, las variables locales de la función externa pueden permanecer accesibles después de regresar de ella. Esto se demuestra anteriormente, porque llamamos a la función say2() después de haber regresado de sayHello2() . Observe que el código que llamamos hace referencia al text variable, que era una variable local de la función sayHello2() .

function() { console.log(text); } // Output of say2.toString();

En cuanto a la salida de say2.toString() , podemos ver que el código se refiere al text variable. La función anónima puede hacer referencia al text que contiene el valor 'Hello Bob' porque las variables locales de sayHello2() se han mantenido vivas en secreto en un cierre.

El genio es que en JavaScript una referencia de función también tiene una referencia secreta al cierre en el 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, los cierres parecen ser muy difíciles de entender cuando lees sobre ellos, 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 cierres 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() {
  // Local variable that ends up within closure
  var num = 42;
  var say = function() { console.log(num); }
  num++;
  return say;
}
var sayNumber = say667();
sayNumber(); // logs 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() {
  // Local variable that ends up within closure
  var num = 42;
  // Store some references to functions as global variables
  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 al mismo cierre: las variables locales de setupSomeGlobals() cuando se definieron las tres funciones.

Tenga en cuenta que en el ejemplo anterior, si llama a setupSomeGlobals() nuevamente, se setupSomeGlobals() un nuevo cierre (stack-frame!). Las antiguas gLogNumber , gIncreaseNumber , gSetNumber se sobrescriben con nuevas funciones que tienen el nuevo cierre. (En JavaScript, cada vez que declara 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 el cierre contiene variables locales que se declararon dentro de la función externa antes de salir. Tenga 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, puede 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); }
    // Local variable that ends up within closure
    var alice = 'Hello Alice';
    return say;
}
sayAlice()();// logs "Hello Alice"

Difícil: también tenga en cuenta que la variable say también está dentro del cierre, 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 inside.

Ejemplo 6

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

Debe comprender la función de "elevación variable" en Javascript para comprender 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]);
    // Using j only to help prevent confusion -- could use i.
    for (var j = 0; j < fnlist.length; j++) {
        fnlist[j]();
    }
}

 testList() //logs "item2 undefined" 3 times

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á tan familiarizado con las funciones anónimas, piense en es como

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

Tenga en cuenta que cuando ejecuta el ejemplo, ¡ "item2 undefined" se registra tres veces! Esto se debe a que, al igual que en los ejemplos anteriores, solo hay un cierre para las variables locales para buildList (que son result , i y item ). Cuando las funciones anónimas se llaman en la línea fnlist[j]() ; todos usan el mismo cierre único, y usan el valor actual para i y el item dentro de ese cierre (donde i tiene un valor de 3 porque el bucle se había completado, y el item tiene un valor de 'item2' ). Tenga en cuenta que estamos indexando desde 0, por lo que el 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 del item variable (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 result la matriz tiene su propio cierre; cuando se ejecuta el ejemplo, la salida es la siguiente:

item0 undefined
item1 undefined
item2 undefined

Si la variable i también se define usando let 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 un cierre separado.

function newClosure(someNum, someRef) {
    // Local variables that end up within closure
    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;

Resumen

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

Puntos finales:

  • Cuando se utiliza la function dentro de otra función, se utiliza un cierre.
  • Siempre que use eval() dentro de una función, se usa un cierre. El texto que eval puede hacer referencia a las variables locales de la función, y dentro de eval incluso puede crear nuevas variables locales utilizando eval('var foo = …')
  • Cuando utiliza la new Function(…) (el constructor de funciones ) dentro de una función, no crea un cierre. (La nueva función no puede hacer referencia a las variables locales de la función externa).
  • Un cierre en JavaScript es como mantener una copia de todas las variables locales, tal como eran cuando una función salía.
  • Probablemente es mejor pensar que un cierre siempre se crea solo una entrada a una función, y las variables locales se agregan a ese cierre.
  • Se mantiene un nuevo conjunto de variables locales cada vez que se llama a una función con un cierre (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 cierre "oculto". No creo que el código JavaScript pueda realmente averiguar si una referencia de función tiene un cierre o no.
  • Si está intentando realizar modificaciones dinámicas en el código fuente (por ejemplo: myFunction = Function(myFunction.toString().replace(/Hello/,'Hola')); ), no funcionará si myFunction es un cierre ( 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 puede obtener cierres en más de un nivel.
  • Creo que normalmente un cierre es un término para la función junto con las variables que se capturan. Tenga en cuenta que no uso esa definición en este artículo!
  • Sospecho que los cierres en JavaScript difieren de los que se encuentran normalmente en los lenguajes funcionales.

Campo de golf

Gracias

Si acaba de aprender acerca de los cierres (¡aquí o en otro lugar!), Entonces me interesa cualquier comentario de usted sobre cualquier cambio que pueda sugerir que pueda aclarar este artículo. Envíe un correo electrónico a morrisjohns.com (morris_closure @). Tenga en cuenta que no soy un gurú en JavaScript, ni en cierres.

La publicación original de Morris se puede encontrar en el Archivo de Internet .


¿Puedes explicar los cierres a un niño de 5 años? *

Todavía creo que la explicación de Google funciona muy bien y es concisa:

/*
*    When a function is defined in another function and it
*    has access to the outer function's context even after
*    the outer function returns.
*
* An important concept to learn in JavaScript.
*/

function outerFunction(someNum) {
    var someString = 'Hey!';
    var content = document.getElementById('content');
    function innerFunction() {
        content.innerHTML = someNum + ': ' + someString;
        content = null; // Internet Explorer memory leak for DOM reference
    }
    innerFunction();
}

outerFunction(1);​

* AC # pregunta


Los cierres son simples:

El siguiente ejemplo simple cubre todos los puntos principales de los cierres de JavaScript. *

Aquí hay una fábrica que produce calculadoras que pueden sumar y multiplicar:

function make_calculator() {
  var n = 0; // this calculator stores a single number n
  return {
    add: function(a) {
      n += a;
      return n;
    },
    multiply: function(a) {
      n *= a;
      return n;
    }
  };
}

first_calculator = make_calculator();
second_calculator = make_calculator();

first_calculator.add(3); // returns 3
second_calculator.add(400); // returns 400

first_calculator.multiply(11); // returns 33
second_calculator.multiply(10); // returns 4000

El punto clave: cada llamada a make_calculatorcrea una nueva variable local n, que continúa siendo utilizable por esa calculadora addy multiplyfunciona mucho después de las make_calculatordevoluciones.

Si está familiarizado con los cuadros de pila, estas calculadoras parecen extrañas: ¿Cómo pueden seguir accediendo ndespués de las make_calculatordevoluciones? La respuesta es imaginar que JavaScript no usa "marcos de pila", sino que utiliza "marcos de pila", que pueden persistir después de la llamada a la función que los generó.

Las funciones internas como addy multiply, que las variables de acceso declaradas en una función externa ** , se denominan cierres .

Eso es prácticamente todo lo que hay para los cierres.


* Por ejemplo, cubre todos los puntos en el artículo "Cierres para tontos" que se encuentra en otra respuesta , excepto en el ejemplo 6, que simplemente muestra que las variables se pueden usar antes de que se declaren, un hecho que hay que saber pero que no tiene relación alguna con los cierres. También cubre todos los puntos en la respuesta aceptada , excepto los puntos (1) que las funciones copian sus argumentos en las variables locales (los argumentos de la función nombrada) y (2) que copiar números crea un nuevo número, pero copiando una referencia de objeto Te da otra referencia al mismo objeto. Estos también son buenos para saber, pero nuevamente no están relacionados con los cierres. También es muy similar al ejemplo en esta respuesta pero un poco más corto y menos abstracto. No cubre el punto deEsta respuesta o este comentario , que es que JavaScript dificulta la conexión de la corrienteel valor de una variable de bucle en su función interna: el paso de "conexión" solo se puede realizar con una función auxiliar que encierre su función interna y se invoque en cada iteración de bucle. (Estrictamente hablando, la función interna accede a la copia de la variable de la función auxiliar, en lugar de tener algo conectado). Una vez más, es muy útil al crear cierres, pero no es parte de lo que es un cierre o cómo funciona. Existe una confusión adicional debido a que los cierres funcionan de manera diferente en lenguajes funcionales como ML, donde las variables están vinculadas a los valores en lugar de al espacio de almacenamiento, lo que proporciona un flujo constante de personas que entienden los cierres de una manera (es decir, la forma de "conexión"). Simplemente incorrecto para JavaScript, donde las variables siempre están vinculadas al espacio de almacenamiento y nunca a los valores.

** Cualquier función externa, si hay varias anidadas, o incluso en el contexto global, como esta respuesta apunta claramente.


Este es un intento de aclarar varios (posibles) malentendidos sobre los cierres que aparecen en algunas de las otras respuestas.

  • Un cierre no solo se crea cuando devuelves una función interna. De hecho, la función de encerramiento no necesita regresar para poder crear su cierre. En su lugar, puede asignar su función interna a una variable en un ámbito externo, o pasarla como un argumento a otra función a la que se pueda llamar de inmediato o en cualquier momento posterior.Por lo tanto, el cierre de la función de encierro probablemente se crea tan pronto como se llama a la función de encierro, ya que cualquier función interna tiene acceso a ese cierre cada vez que se llama a la función interna, antes o después de que la función de encierre retorne.
  • Un cierre no hace referencia a una copia de los valores antiguos de las variables en su alcance. Las variables en sí mismas son parte del cierre, por lo que el valor visto al acceder a una de esas variables es el último valor en el momento en que se accede. Esta es la razón por la que las funciones internas creadas dentro de los bucles pueden ser complicadas, ya que cada una tiene acceso a las mismas variables externas en lugar de tomar una copia de las variables en el momento en que se crea o llama la función.
  • Las "variables" en un cierre incluyen cualquier función nombrada declarada dentro de la función. También incluyen argumentos de la función. Un cierre también tiene acceso a sus variables de cierre de cierre, hasta el alcance global.
  • Los cierres utilizan la memoria, pero no causan pérdidas de memoria ya que JavaScript por sí solo limpia sus propias estructuras circulares a las que no se hace referencia. Las fugas de memoria de Internet Explorer que involucran cierres se crean cuando falla al desconectar los valores de los atributos DOM que hacen referencia a los cierres, manteniendo así las referencias a estructuras posiblemente circulares.

Los cierres son difíciles de explicar porque se utilizan para hacer que funcione un poco de comportamiento que, de manera intuitiva, todos esperan que funcione. Encuentro la mejor manera de explicarlos (y la forma en que aprendí lo que hacen) es imaginar la situación sin ellos:

    var bind = function(x) {
        return function(y) { return x + y; };
    }
    
    var plus5 = bind(5);
    console.log(plus5(3));

¿Qué pasaría aquí si JavaScript no conociera los cierres? Simplemente reemplace la llamada en la última línea por su cuerpo de método (que es básicamente lo que hacen las llamadas de función) y obtendrá:

console.log(x + 3);

Ahora, ¿dónde está la definición de x ? No lo definimos en el ámbito actual. La única solución es dejar que plus5 lleve su alcance (o, más bien, el alcance de su padre). De esta manera, x está bien definido y está vinculado al valor 5.


Tomando en serio la pregunta, deberíamos descubrir de qué forma es capaz cognitivamente un niño típico de 6 años, aunque hay que reconocer que uno que está interesado en JavaScript no es tan típico.

Sobre Desarrollo Infantil: 5 a 7 años dice:

Su hijo podrá seguir instrucciones de dos pasos. Por ejemplo, si le dice a su hijo: "Ve a la cocina y tráeme una bolsa de basura" podrán recordar esa dirección.

Podemos usar este ejemplo para explicar los cierres, como sigue:

La cocina es un cierre que tiene una variable local, llamada trashBags . Hay una función dentro de la cocina llamada getTrashBag que obtiene una bolsa de basura y la devuelve.

Podemos codificar esto en JavaScript de esta manera:

function makeKitchen() {
  var trashBags = ['A', 'B', 'C']; // only 3 at first

  return {
    getTrashBag: function() {
      return trashBags.pop();
    }
  };
}

var kitchen = makeKitchen();

console.log(kitchen.getTrashBag()); // returns trash bag C
console.log(kitchen.getTrashBag()); // returns trash bag B
console.log(kitchen.getTrashBag()); // returns trash bag A

Otros puntos que explican por qué los cierres son interesantes:

  • Cada vez que se llama a makeKitchen() , se crea un nuevo cierre con sus propias trashBags separadas.
  • La variable trashBags es local en el interior de cada cocina y no es accesible desde el exterior, pero la función interna en la propiedad getTrashBag sí tiene acceso a ella.
  • Cada llamada de función crea un cierre, pero no habría necesidad de mantener el cierre alrededor a menos que una función interna, que tiene acceso al interior del cierre, pueda ser llamada desde fuera del cierre. Devolver el objeto con la función getTrashBag hace aquí.

Wikipedia sobre cierres :

En informática, un cierre es una función junto con un entorno de referencia para los nombres no locales (variables libres) de esa función.

Técnicamente, en JavaScript , cada función es un cierre . Siempre tiene acceso a las variables definidas en el ámbito que lo rodea.

Dado que la construcción de definición de alcance en JavaScript es una función , no un bloque de código como en muchos otros idiomas, lo que generalmente entendemos por cierre en JavaScript es una función que trabaja con variables no locales definidas en la función circundante ya ejecutada .

Los cierres a menudo se usan para crear funciones con algunos datos privados ocultos (pero no siempre es así).

var db = (function() {
    // Create a hidden object, which will hold the data
    // it's inaccessible from the outside.
    var data = {};

    // Make a function, which will provide some access to the data.
    return function(key, val) {
        if (val === undefined) { return data[key] } // Get
        else { return data[key] = val } // Set
    }
    // We are calling the anonymous surrounding function,
    // returning the above inner function, which is a closure.
})();

db('x')    // -> undefined
db('x', 1) // Set x to 1
db('x')    // -> 1
// It's impossible to access the data object itself.
// We are able to get or set individual it.

ems

El ejemplo anterior está usando una función anónima, que se ejecutó una vez. Pero no tiene que serlo. Puede ser nombrado (por ejemplo mkdb) y ejecutarse más tarde, generando una función de base de datos cada vez que se invoca. Cada función generada tendrá su propio objeto de base de datos oculto. Otro ejemplo de uso de cierres es cuando no devolvemos una función, sino un objeto que contiene múltiples funciones para diferentes propósitos, cada una de las cuales tiene acceso a los mismos datos.


Bien, hablando con un niño de 6 años, posiblemente usaría las siguientes asociaciones.

Imagínate: estás jugando con tus hermanos pequeños en toda la casa, te estás moviendo con tus juguetes y llevándolos a la habitación de tu hermano mayor. Al cabo de un rato, tu hermano regresó de la escuela y fue a su habitación, y él lo encerró dentro, así que ahora ya no podrías acceder a los juguetes que quedaban allí de forma directa. Pero podrías golpear la puerta y pedirle a tu hermano esos juguetes. Esto se llama cierre de juguete ; tu hermano lo inventó para ti, y ahora está en el ámbito externo .

Compare con una situación en la que una puerta estaba cerrada con llave y nadie adentro (ejecución de la función general), luego ocurre un incendio local que quema la habitación (recolector de basura: D), y luego se construyó una nueva habitación y ahora puede irse hay otros juguetes allí (nueva instancia de función), pero nunca se obtienen los mismos juguetes que quedaron en la primera instancia de la sala.

Para un niño avanzado pondría algo como lo siguiente. No es perfecto, pero te hace sentir lo que es:

function playingInBrothersRoom (withToys) {
  // We closure toys which we played in the brother's room. When he come back and lock the door
  // your brother is supposed to be into the outer [[scope]] object now. Thanks god you could communicate with him.
  var closureToys = withToys || [],
      returnToy, countIt, toy; // Just another closure helpers, for brother's inner use.

  var brotherGivesToyBack = function (toy) {
    // New request. There is not yet closureToys on brother's hand yet. Give him a time.
    returnToy = null;
    if (toy && closureToys.length > 0) { // If we ask for a specific toy, the brother is going to search for it.

      for ( countIt = closureToys.length; countIt; countIt--) {
        if (closureToys[countIt - 1] == toy) {
          returnToy = 'Take your ' + closureToys.splice(countIt - 1, 1) + ', little boy!';
          break;
        }
      }
      returnToy = returnToy || 'Hey, I could not find any ' + toy + ' here. Look for it in another room.';
    }
    else if (closureToys.length > 0) { // Otherwise, just give back everything he has in the room.
      returnToy = 'Behold! ' + closureToys.join(', ') + '.';
      closureToys = [];
    }
    else {
      returnToy = 'Hey, lil shrimp, I gave you everything!';
    }
    console.log(returnToy);
  }
  return brotherGivesToyBack;
}
// You are playing in the house, including the brother's room.
var toys = ['teddybear', 'car', 'jumpingrope'],
    askBrotherForClosuredToy = playingInBrothersRoom(toys);

// The door is locked, and the brother came from the school. You could not cheat and take it out directly.
console.log(askBrotherForClosuredToy.closureToys); // Undefined

// But you could ask your brother politely, to give it back.
askBrotherForClosuredToy('teddybear'); // Hooray, here it is, teddybear
askBrotherForClosuredToy('ball'); // The brother would not be able to find it.
askBrotherForClosuredToy(); // The brother gives you all the rest
askBrotherForClosuredToy(); // Nothing left in there

Como puede ver, los juguetes que quedan en la habitación aún son accesibles a través del hermano y no importa si la habitación está cerrada con llave. Aquí hay un jsbin para jugar con él.


Cómo se lo explicaría a un niño de seis años:

¿Sabes cómo los adultos pueden ser dueños de una casa y la llaman su hogar? Cuando una madre tiene un hijo, el niño realmente no posee nada, ¿verdad? Pero los padres son dueños de una casa, por lo que cada vez que alguien le pregunta al niño "¿Dónde está tu casa?", Él / ella puede responder "esa casa" y señalar la casa de sus padres. Un "cierre" es la capacidad del niño de poder siempre (incluso si está en el extranjero) poder decir que tiene un hogar, aunque en realidad los padres son los dueños de la casa.


Las funciones de JavaScript pueden acceder a su:

  1. Argumentos
  2. Locales (es decir, sus variables locales y funciones locales)
  3. Medio ambiente, que incluye:
    • Globales, incluyendo el DOM
    • cualquier cosa en funciones externas

Si una función accede a su entorno, entonces la función es un cierre.

Tenga en cuenta que las funciones externas no son necesarias, aunque sí ofrecen beneficios que no menciono aquí. Al acceder a los datos en su entorno, un cierre mantiene esos datos vivos. En el subcaso de funciones externas / internas, una función externa puede crear datos locales y eventualmente salir, y sin embargo, si alguna función interna sobrevive después de que la función externa finaliza, las funciones internas mantienen la información local de la función externa viva.

Ejemplo de un cierre que utiliza el entorno global:

Imagine que los eventos del botón de desbordamiento de pila y de botón de votación se implementan como cierres, voteUp_click y voteDown_click, que tienen acceso a variables externas isVotedUp e isVotedDown, que se definen globalmente. (En aras de la simplicidad, me refiero a los botones de votación de preguntas de , no a la matriz de botones de respuesta de votos).

Cuando el usuario hace clic en el botón Votar, la función voteUp_click verifica si isVotedDown == true para determinar si se debe votar o simplemente cancelar una votación negativa. La función voteUp_click es un cierre porque está accediendo a su entorno.

var isVotedUp = false;
var isVotedDown = false;

function voteUp_click() {
  if (isVotedUp)
    return;
  else if (isVotedDown)
    SetDownVote(false);
  else
    SetUpVote(true);
}

function voteDown_click() {
  if (isVotedDown)
    return;
  else if (isVotedUp)
    SetUpVote(false);
  else
    SetDownVote(true);
}

function SetUpVote(status) {
  isVotedUp = status;
  // Do some CSS stuff to Vote-Up button
}

function SetDownVote(status) {
  isVotedDown = status;
  // Do some CSS stuff to Vote-Down button
}

Las cuatro de estas funciones son cierres, ya que todas acceden a su entorno.


Sé que ya hay muchas soluciones, pero creo que este pequeño y simple script puede ser útil para demostrar el concepto:

// makeSequencer will return a "sequencer" function
var makeSequencer = function() {
    var _count = 0; // not accessible outside this function
    var sequencer = function () {
        return _count++;
    }
    return sequencer;
}

var fnext = makeSequencer();
var v0 = fnext();     // v0 = 0;
var v1 = fnext();     // v1 = 1;
var vz = fnext._count // vz = undefined

Una respuesta para un niño de seis años (asumiendo que él sabe qué es una función y qué es una variable, y qué datos son):

Las funciones pueden devolver datos. Un tipo de datos que puede devolver desde una función es otra función. Cuando se devuelve esa nueva función, todas las variables y argumentos utilizados en la función que la creó no desaparecen. En su lugar, esa función principal "se cierra". En otras palabras, nada puede mirar dentro de él y ver las variables que utilizó, excepto la función que devolvió. Esa nueva función tiene una capacidad especial para mirar hacia atrás dentro de la función que la creó y ver los datos dentro de ella.

function the_closure() {
  var x = 4;
  return function () {
    return x; // Here, we look back inside the_closure for the value of x
  }
}

var myFn = the_closure();
myFn(); //=> 4

Otra forma realmente simple de explicarlo es en términos de alcance:

Cada vez que cree un alcance más pequeño dentro de un alcance más amplio, el alcance más pequeño siempre podrá ver qué hay en el alcance más amplio.


Como padre de un niño de 6 años, actualmente enseña a niños pequeños (y es un principiante relativo a la codificación sin educación formal para que se requieran correcciones), creo que la lección se mantendría mejor a través del juego práctico. Si el niño de 6 años está listo para entender qué es un cierre, entonces ellos tienen la edad suficiente para tener una oportunidad. Yo sugeriría pegar el código en jsfiddle.net, explicar un poco y dejarlos solos para inventar una canción única. El texto explicativo a continuación es probablemente más apropiado para un niño de 10 años.

function sing(person) {

    var firstPart = "There was " + person + " who swallowed ";

    var fly = function() {
        var creature = "a fly";
        var result = "Perhaps she'll die";
        alert(firstPart + creature + "\n" + result);
    };

    var spider = function() {
        var creature = "a spider";
        var result = "that wiggled and jiggled and tickled inside her";
        alert(firstPart + creature + "\n" + result);
    };

    var bird = function() {
        var creature = "a bird";
        var result = "How absurd!";
        alert(firstPart + creature + "\n" + result);
    };

    var cat = function() {
        var creature = "a cat";
        var result = "Imagine That!";
        alert(firstPart + creature + "\n" + result);
    };

    fly();
    spider();
    bird();
    cat();
}

var person="an old lady";

sing(person);

INSTRUCCIONES

DATOS: Los datos son una colección de hechos. Pueden ser números, palabras, medidas, observaciones o incluso simplemente descripciones de cosas. No puedes tocarlo, olerlo o saborearlo. Puedes escribirlo, hablarlo y escucharlo. Puedes usarlo para crear un toque de olor y gusto usando una computadora. Puede ser útil por una computadora usando código.

CÓDIGO: Toda la escritura anterior se llama código . Está escrito en JavaScript.

JAVASCRIPT: JavaScript es un lenguaje. Como el inglés o el francés o el chino son idiomas. Hay muchos idiomas que entienden las computadoras y otros procesadores electrónicos. Para que JavaScript sea comprendido por una computadora, se necesita un intérprete. Imagínese si un maestro que solo habla ruso viene a enseñar su clase en la escuela. Cuando el profesor dice "все садятся", la clase no entendería. Pero, afortunadamente, tienes un alumno ruso en tu clase que les dice a todos que esto significa "todos se sientan", así que todos lo hacen. La clase es como una computadora y el alumno ruso es el intérprete. Para JavaScript, el intérprete más común se llama navegador.

NAVEGADOR: cuando se conecta a Internet en una computadora, tableta o teléfono para visitar un sitio web, utiliza un navegador. Algunos ejemplos que puede saber son Internet Explorer, Chrome, Firefox y Safari. El navegador puede comprender JavaScript y decirle a la computadora lo que debe hacer. Las instrucciones de JavaScript se llaman funciones.

FUNCIÓN: Una función en JavaScript es como una fábrica. Puede ser una pequeña fábrica con una sola máquina dentro. O podría contener muchas otras pequeñas fábricas, cada una con muchas máquinas que realizan diferentes trabajos. En una fábrica de ropa de la vida real, es posible que tengas montones de telas y bobinas de hilo que entran y te salen camisetas y jeans. Nuestra fábrica de JavaScript solo procesa datos, no puede coser, perforar un agujero o fundir metal. En nuestra fábrica de JavaScript los datos entran y los datos salen.

Todo este material de datos suena un poco aburrido, pero es realmente genial; podríamos tener una función que le diga a un robot qué hacer para la cena. Digamos que te invito a ti ya tu amigo a mi casa. A ti te gustan más las piernas de pollo, me gustan las salchichas, tu amigo siempre quiere lo que quieres y mi amigo no come carne.

No tengo tiempo para ir de compras, por lo que la función necesita saber qué tenemos en la nevera para tomar decisiones. Cada ingrediente tiene un tiempo de cocción diferente y queremos que el robot sirva todo al mismo tiempo. Necesitamos proporcionar la función con los datos sobre lo que nos gusta, la función podría "hablar" con el refrigerador y la función podría controlar el robot.

Una función normalmente tiene un nombre, paréntesis y llaves. Me gusta esto:

function cookMeal() {  /*  STUFF INSIDE THE FUNCTION  */  }

Tenga en cuenta que /*...*/y //detenga el código que lee el navegador.

NOMBRE: Puede llamar a una función casi cualquier palabra que desee. El ejemplo "cookMeal" es típico de unir dos palabras y darle al segundo una letra mayúscula al principio, pero esto no es necesario. No puede tener un espacio y no puede ser un número por sí solo.

PARENTES: Los "paréntesis" o ()son el buzón en la puerta de la fábrica de la función JavaScript o un buzón en la calle para enviar paquetes de información a la fábrica. A veces, el buzón de correos puede estar marcado, por ejemplo cookMeal(you, me, yourFriend, myFriend, fridge, dinnerTime) , en cuyo caso usted sabe qué datos debe proporcionar.

BRACES: "Braces" que se ven así {}son las ventanas tintadas de nuestra fábrica. Desde dentro de la fábrica se puede ver, pero desde afuera no se puede ver.

El código largo ejemplo anterior

Nuestro código comienza con la palabra función , ¡así que sabemos que es una! Luego el nombre de la función cantar , esa es mi propia descripción de lo que trata la función. Luego los paréntesis () . Los paréntesis están siempre ahí para una función. A veces están vacíos, ya veces tienen algo en Éste tiene una palabra en.: (person). Después de esto hay una abrazadera como esta {. Esto marca el inicio de la función sing () . Tiene un compañero que marca el final de sing () así.}

function sing(person) {  /* STUFF INSIDE THE FUNCTION */  }

Por lo tanto, esta función podría tener algo que ver con el canto y podría necesitar algunos datos sobre una persona. Tiene instrucciones dentro para hacer algo con esos datos.

Ahora, después de la función sing () , cerca del final del código está la línea

var person="an old lady";

VARIABLE: Las letras var representan "variable". Una variable es como un sobre. En el exterior este sobre está marcado como "persona". En el interior contiene una hoja de papel con la información que nuestra función necesita, algunas letras y espacios se unen como un trozo de cuerda (se llama una cuerda) que hace que una frase lea "una anciana". Nuestro sobre podría contener otros tipos de cosas como números (llamados enteros), instrucciones (llamadas funciones), listas (llamadas matrices ). Debido a que esta variable está escrita fuera de todas las llaves {}, y porque puede ver a través de las ventanas tintadas cuando está dentro de las llaves, esta variable se puede ver desde cualquier parte del código. Llamamos a esto una 'variable global'.

VARIABLE GLOBAL: persona es una variable global, lo que significa que si cambia su valor de "una anciana" a "un hombre joven", la persona seguirá siendo un hombre joven hasta que decida cambiarlo nuevamente y que cualquier otra función en El código puede ver que es un hombre joven. Presione el F12botón o mire la configuración de Opciones para abrir la consola del desarrollador de un navegador y escriba "persona" para ver cuál es este valor. Escriba person="a young man"para cambiarlo y luego escriba "persona" nuevamente para ver que ha cambiado.

Después de esto tenemos la línea

sing(person);

Esta línea está llamando a la función, como si estuviera llamando a un perro

"Vamos , canta , ven y hazte con la persona !"

Cuando el navegador haya cargado el código JavaScript y haya alcanzado esta línea, se iniciará la función. Pongo la línea al final para asegurarme de que el navegador tenga toda la información que necesita para ejecutarlo.

Las funciones definen las acciones, la función principal es sobre el canto. Contiene una variable llamada firstPart que se aplica al canto de la persona que se aplica a cada uno de los versos de la canción: "Hubo" + persona + "que tragó". Si escribe firstPart en la consola, no obtendrá una respuesta porque la variable está bloqueada en una función: el navegador no puede ver dentro de las ventanas tintadas de las llaves.

CIERRES: Los cierres son las funciones más pequeñas que están dentro de la función big sing () . Las pequeñas fábricas dentro de la gran fábrica. Cada uno tiene sus propias llaves, lo que significa que las variables dentro de ellos no pueden verse desde el exterior. Es por eso que los nombres de las variables ( criatura y resultado ) se pueden repetir en los cierres pero con diferentes valores. Si escribe estos nombres de variable en la ventana de la consola, no obtendrá su valor porque está oculto por dos capas de ventanas polarizadas.

Todos los cierres saben lo que es la variable de la función sing () llamada firstPart , porque pueden ver desde sus ventanas tintadas.

Después de los cierres vienen las líneas.

fly();
spider();
bird();
cat();

La función sing () llamará a cada una de estas funciones en el orden en que se dan. Luego se realizará el trabajo de la función sing ().


Estás durmiendo e invitas a Dan. Le dices a Dan que traiga un controlador XBox.

Dan invita a Paul. Dan le pide a Paul que traiga un controlador. ¿Cuántos controladores fueron traídos a la fiesta?

function sleepOver(howManyControllersToBring) {

    var numberOfDansControllers = howManyControllersToBring;

    return function danInvitedPaul(numberOfPaulsControllers) {
        var totalControllers = numberOfDansControllers + numberOfPaulsControllers;
        return totalControllers;
    }
}

var howManyControllersToBring = 1;

var inviteDan = sleepOver(howManyControllersToBring);

// The only reason Paul was invited is because Dan was invited. 
// So we set Paul's invitation = Dan's invitation.

var danInvitedPaul = inviteDan(howManyControllersToBring);

alert("There were " + danInvitedPaul + " controllers brought to the party.");

Hace un tiempo escribí una publicación en el blog explicando los cierres. Esto es lo que dije sobre los cierres en términos de por qué querría uno.

Los cierres son una forma de permitir que una función tenga variables privadas persistentes, es decir, variables que solo una función conoce, en las que puede realizar un seguimiento de la información de los tiempos anteriores en que se ejecutó.

En ese sentido, dejan que una función actúe un poco como un objeto con atributos privados.

Publicación completa:

Entonces, ¿qué son estas cosas de cierre?


Preparé un tutorial interactivo de JavaScript para explicar cómo funcionan los cierres. ¿Qué es un cierre?

Aquí está uno de los ejemplos:

var create = function (x) {
    var f = function () {
        return x; // We can refer to x here!
    };
    return f;
};
// 'create' takes one argument, creates a function

var g = create(42);
// g is a function that takes no arguments now

var y = g();
// y is 42 here

Simplemente los señalaría a la página de cierres de Mozilla . Es la mejor, más concisa y simple explicación de los conceptos básicos de cierre y uso práctico que he encontrado. Es altamente recomendable para cualquiera que esté aprendiendo JavaScript.

Y sí, incluso lo recomendaría a un niño de 6 años: si el niño de 6 años está aprendiendo sobre los cierres, entonces es lógico que estén listos para comprender la explicación concisa y simple que se proporciona en el artículo.


Un cierre es muy parecido a un objeto. Se crea una instancia cuando llamas a una función.

El alcance de un cierre en JavaScript es léxico, lo que significa que todo lo que está contenido dentro de la función a la que pertenece el cierre , tiene acceso a cualquier variable que esté en él.

Una variable está contenida en el cierre si

  1. asignarlo con var foo=1; o
  2. solo escribe var foo;

Si una función interna (una función contenida dentro de otra función) accede a dicha variable sin definirla en su propio ámbito con var, modifica el contenido de la variable en el cierre externo .

Un cierre sobrevive al tiempo de ejecución de la función que lo generó. Si otras funciones logran salir del cierre / alcance en el que están definidas (por ejemplo, como valores de retorno), esas continuarán haciendo referencia a ese cierre .

Ejemplo

function example(closure) {
  // define somevariable to live in the closure of example
  var somevariable = 'unchanged';

  return {
    change_to: function(value) {
      somevariable = value;
    },
    log: function(value) {
      console.log('somevariable of closure %s is: %s',
        closure, somevariable);
    }
  }
}

closure_one = example('one');
closure_two = example('two');

closure_one.log();
closure_two.log();
closure_one.change_to('some new value');
closure_one.log();
closure_two.log();

Salida

somevariable of closure one is: unchanged
somevariable of closure two is: unchanged
somevariable of closure one is: some new value
somevariable of closure two is: unchanged

Un cierre es donde una función interna tiene acceso a variables en su función externa. Esa es probablemente la explicación de una línea más simple que puede obtener para los cierres.





closures