¿Cómo funcionan los cierres de JavaScript?

7626

¿Cómo le explicaría los cierres de JavaScript a alguien que conozca los conceptos que los componen (por ejemplo, funciones, variables y similares), pero que no entienda los cierres en sí mismos?

He visto el ejemplo de Scheme en Wikipedia, pero desafortunadamente no ayudó.

0
7867
+100

Un cierre es una combinación de:

  1. Una función, y
  2. Una referencia al alcance externo de esa función (entorno léxico)

Un entorno léxico es parte de cada contexto de ejecución (marco de pila) y es un mapa entre identificadores (es decir, nombres de variables locales) y valores.

Cada función en JavaScript mantiene una referencia a su entorno léxico externo. Esta referencia se utiliza para configurar el contexto de ejecución creado cuando se invoca una función. Esta referencia permite que el código dentro de la función "vea" las variables declaradas fuera de la función, independientemente de cuándo y dónde se llame a la función.

Si una función fue llamada por una función, que a su vez fue llamada por otra función, entonces se crea una cadena de referencias a entornos léxicos externos. Esta cadena se llama cadena de alcance.

En el siguiente código, innerforma un cierre con el entorno léxico del contexto de ejecución creado cuando foose invoca, cerrando sobre la variable secret:

function foo() {
  const secret = Math.trunc(Math.random()*100)
  return function inner() {
    console.log(`The secret number is ${secret}.`)
  }
}
const f = foo() // `secret` is not directly accessible from outside `foo`
f() // The only way to retrieve `secret`, is to invoke `f`

En otras palabras: en JavaScript, las funciones llevan una referencia a una "caja de estado" privada, a la que solo ellos (y cualquier otra función declarada dentro del mismo entorno léxico) tienen acceso. Esta casilla del estado es invisible para quien llama a la función, lo que ofrece un excelente mecanismo para ocultar y encapsular datos.

Y recuerde: las funciones en JavaScript se pueden transmitir como variables (funciones de primera clase), lo que significa que estos pares de funcionalidad y estado se pueden transmitir en su programa: de manera similar a cómo podría pasar una instancia de una clase en C ++.

Si JavaScript no tuviera cierres, entonces tendrían que pasarse más estados entre funciones explícitamente , haciendo que las listas de parámetros sean más largas y el código más ruidoso.

Entonces, si desea que una función siempre tenga acceso a un estado privado, puede usar un cierre.

... y con frecuencia nos hacemos queremos asociar el estado de una función. Por ejemplo, en Java o C ++, cuando agrega una variable de instancia privada y un método a una clase, está asociando el estado con la funcionalidad.

En C y en la mayoría de los otros lenguajes comunes, después de que una función regresa, ya no se puede acceder a todas las variables locales 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. De esta manera, en el código anterior, secretpermanece disponible para el objeto de función inner, después de que se haya devuelto foo.

Usos de cierres

Los cierres son útiles siempre que necesite un estado privado asociado con una función. Este es un escenario muy común, y recuerde: JavaScript no tenía una sintaxis de clase hasta 2015, y todavía no tiene una sintaxis de campo privado. Los cierres satisfacen esta necesidad.

Variables de instancia privada

En el siguiente código, la función se toStringcierra sobre los detalles del automóvil.

function Car(manufacturer, model, year, color) {
  return {
    toString() {
      return `${manufacturer} ${model} (${year}, ${color})`
    }
  }
}
const car = new Car('Aston Martin','V8 Vantage','2012','Quantum Silver')
console.log(car.toString())

Programación funcional

En el siguiente código, la función se innercierra sobre ambos fny args.

function curry(fn) {
  const args = []
  return function inner(arg) {
    if(args.length === fn.length) return fn(...args)
    args.push(arg)
    return inner
  }
}

function add(a, b) {
  return a + b
}

const curriedAdd = curry(add)
console.log(curriedAdd(2)(3)()) // 5

Programación orientada a eventos

En el siguiente código, la función se onClickcierra sobre la variable BACKGROUND_COLOR.

const $ = document.querySelector.bind(document)
const BACKGROUND_COLOR = 'rgba(200,200,242,1)'

function onClick() {
  $('body').style.background = BACKGROUND_COLOR
}

$('button').addEventListener('click', onClick)
<button>Set background color</button>

Modularización

En el siguiente ejemplo, todos los detalles de implementación están ocultos dentro de una expresión de función ejecutada inmediatamente. Las funciones ticky toStringcierran sobre el estado privado y las funciones que necesitan para completar su trabajo. Los cierres nos han permitido modularizar y encapsular nuestro código.

let namespace = {};

(function foo(n) {
  let numbers = []
  function format(n) {
    return Math.trunc(n)
  }
  function tick() {
    numbers.push(Math.random() * 100)
  }
  function toString() {
    return numbers.map(format)
  }
  n.counter = {
    tick,
    toString
  }
}(namespace))

const counter = namespace.counter
counter.tick()
counter.tick()
console.log(counter.toString())

Ejemplos de

Ejemplo 1

Este ejemplo muestra que las variables locales no se copian en el cierre: el cierre mantiene una referencia a las propias variables originales . Es como si el stack-frame permaneciera vivo en la memoria incluso después de que la función externa saliera.

function foo() {
  let x = 42
  let inner  = function() { console.log(x) }
  x = x+1
  return inner
}
var f = foo()
f() // logs 43

Ejemplo 2

En el siguiente código, tres métodos log, incrementy updatetodos cerca en el mismo entorno léxico.

Y cada vez que createObjectse llama, se crea un nuevo contexto de ejecución (marco de pila) y se crea una variable completamente nueva x, y se crea un nuevo conjunto de funciones ( logetc.), que se cierran sobre esta nueva variable.

function createObject() {
  let x = 42;
  return {
    log() { console.log(x) },
    increment() { x++ },
    update(value) { x = value }
  }
}

const o = createObject()
o.increment()
o.log() // 43
o.update(5)
o.log() // 5
const p = createObject()
p.log() // 42

Ejemplo 3

Si está usando variables declaradas usando var, tenga cuidado de comprender qué variable está cerrando. Las variables declaradas mediante varse elevan. Esto es un problema mucho menor en JavaScript moderno debido a la introducción de lety const.

En el siguiente código, cada vez que se da la vuelta al ciclo, innerse crea una nueva función , que se cierra i. Pero debido a que var ise iza fuera del ciclo, todas estas funciones internas se cierran sobre la misma variable, lo que significa que el valor final de i(3) se imprime tres veces.

function foo() {
  var result = []
  for (var i = 0; i < 3; i++) {
    result.push(function inner() { console.log(i) } )
  }
  return result
}

const result = foo()
// The following will print `3`, three times...
for (var i = 0; i < 3; i++) {
  result[i]() 
}

Puntos finales:

  • Siempre que se declara una función en JavaScript, se crea un cierre.
  • Devolver un functiondesde dentro de otra función es el ejemplo clásico de cierre, porque el estado dentro de la función externa está implícitamente disponible para la función interna devuelta, incluso después de que la función externa haya completado la ejecución.
  • Siempre que use eval()dentro de una función, se usa un cierre. El texto evalpuede hacer referencia a las variables locales de la función y, en el modo no estricto, incluso puede crear nuevas variables locales utilizando eval('var foo = …').
  • Cuando usa new Function(…)(el constructor de Función ) dentro de una función, no se cierra sobre su entorno léxico: en su lugar, se cierra sobre el contexto global. 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 referencia ( NO una copia) al alcance en el punto de declaración de la función, que a su vez mantiene una referencia a su alcance externo, y así sucesivamente, hasta el objeto global en la parte superior de la cadena de alcance.
  • Se crea un cierre cuando se declara una función; este cierre se utiliza para configurar el contexto de ejecución cuando se invoca la función.
  • Se crea un nuevo conjunto de variables locales cada vez que se llama a una función.

Enlaces

1
  • ¡JavaScript es realmente genial!
    Nearoo
    hace 7 horas
4074

Cada función en JavaScript mantiene un enlace a su entorno léxico externo. Un entorno léxico es un mapa de todos los nombres (por ejemplo, variables, parámetros) dentro de un ámbito, con sus valores.

Entonces, siempre que vea la functionpalabra clave, el código dentro de esa función tiene acceso a las variables declaradas fuera de la función.

function foo(x) {
  var tmp = 3;

  function bar(y) {
    console.log(x + y + (++tmp)); // will log 16
  }

  bar(10);
}

foo(2);

Esto se registrará 16porque la función se barcierra sobre el parámetro xy la variable tmp, los cuales existen en el entorno léxico de la función externa foo.

La función bar, junto con su vínculo con el entorno léxico de la función, fooes un cierre.

No es necesario que una función regrese para crear un cierre. Simplemente en virtud de su declaración, cada función se cierra sobre su entorno léxico envolvente, formando un cierre.

function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + (++tmp)); // will also log 16
  }
}

var bar = foo(2);
bar(10); // 16
bar(10); // 17

La función anterior también registrará 16, porque el código interno baraún puede referirse al argumento xy la variable tmp, aunque ya no estén directamente en el alcance.

Sin embargo, dado tmpque todavía está rondando barel cierre interior , está disponible para ser incrementado. Se incrementará cada vez que llame bar.

El ejemplo más simple de un cierre es este:

var a = 10;

function test() {
  console.log(a); // will output 10
  console.log(b); // will output 6
}
var b = 6;
test();

Cuando se invoca una función de JavaScript, ecse crea un nuevo contexto de ejecución . Junto con los argumentos de la función y el objeto de destino, este contexto de ejecución también recibe un enlace al entorno léxico del contexto de ejecución de la llamada, lo que significa que las variables declaradas en el entorno léxico externo (en el ejemplo anterior, ambos ay b) están disponibles en ec.

Cada función crea un cierre porque cada función tiene un vínculo con su entorno léxico externo.

Tenga en cuenta que las variables en sí mismas son visibles desde dentro de un cierre, no copias.

0
2528

PREFACIO: esta respuesta fue escrita cuando la pregunta era:

Like the old Albert said : "If you can't explain it to a six-year old, you really don't understand it yourself.”. Well I tried to explain JS closures to a 27 years old friend and completely failed.

Can anybody consider that I am 6 and strangely interested in that subject ?

Estoy bastante seguro de que fui una de las pocas personas que intentó tomar la pregunta inicial literalmente. Desde entonces, la pregunta ha mutado varias veces, por lo que mi respuesta ahora puede parecer increíblemente tonta y fuera de lugar. Con suerte, la idea general de la historia sigue siendo divertida para algunos.


Soy un gran fanático de la analogía y la metáfora cuando explico conceptos difíciles, así que déjame probar suerte con una historia.

Había una vez:

Había una princesa ...

function princess() {

Vivía en un mundo maravilloso lleno de aventuras. Conoció a su Príncipe Azul, recorrió su mundo en un unicornio, luchó contra dragones, se encontró con animales que hablaban y muchas otras cosas fantásticas.

    var adventures = [];

    function princeCharming() { /* ... */ }

    var unicorn = { /* ... */ },
        dragons = [ /* ... */ ],
        squirrel = "Hello!";

    /* ... */

Pero siempre tendría que volver a su aburrido mundo de tareas domésticas y adultos.

    return {

Y a menudo les contaba su última y asombrosa aventura como princesa.

        story: function() {
            return adventures[adventures.length - 1];
        }
    };
}

Pero todo lo que verían es una niña ...

var littleGirl = princess();

... contando historias sobre magia y fantasía.

littleGirl.story();

Y aunque los adultos conocían a las princesas reales, nunca creerían en los unicornios ni en los dragones porque nunca podrían verlos. Los mayores decían que solo existían dentro de la imaginación de la niña.

Pero conocemos la verdad real; que la niñita con la princesa adentro ...

... es realmente una princesa con una niña adentro.

4
  • 368
    Me encanta esta explicación, de verdad. Para aquellos que lo leen y no lo siguen, la analogía es la siguiente: la función princesa () es un ámbito complejo que contiene datos privados. Fuera de la función, los datos privados no se pueden ver ni acceder. La princesa guarda los unicornios, dragones, aventuras, etc. en su imaginación (datos privados) y los adultos no pueden verlos por sí mismos. PERO la imaginación de la princesa se captura en el cierre de la story()función, que es la única interfaz que la littleGirlinstancia expone al mundo de la magia. 28 de febrero de 2013 a las 7:49
  • 2
    Tener valores indefinidos hace que sea más difícil de entender. Aquí está la verdadera historia jsfiddle.net/rjdx34k0/3
    Hugolpz
    2 sep.2020 a las 19:13
  • Oh, bueno, estuve tan cerca de hacer una edición para eliminar lo que pensé que era el espacio extra al principio. Buen trabajo, +1 23/10/20 a las 7:34
  • 1
    Y el Príncipe Azul puede contribuir a sus aventuras, puede matar a todos los dragones para salvarla de peligros como los siguientes: function princeCharming { adventures.push('Honeymoon Trip', 'Skydiving', 'Visiting Somalia'); const pickADragonToKill = dragons.pop(); }
    Shivam
    13 de enero a las 5:15
784

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

Sobre el desarrollo infantil: 5 a 7 años dice:

Your child will be able to follow two-step directions. For example, if you say to your child, "Go to the kitchen and get me a trash bag" they will be able to remember that direction.

Podemos usar este ejemplo para explicar los cierres, de la siguiente manera:

The kitchen is a closure that has a local variable, called trashBags. There is a function inside the kitchen called getTrashBag that gets one trash bag and returns it.

Podemos codificar esto en JavaScript así:

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 makeKitchen()se llama, se crea un nuevo cierre con el suyo propio trashBags.
  • La trashBagsvariable es local en el interior de cada cocina y no es accesible desde el exterior, pero la función interior de la getTrashBagpropiedad 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 se pueda llamar a una función interna, que tiene acceso al interior del cierre, desde fuera del cierre. Devolver el objeto con la getTrashBagfunción hace eso aquí.
0
606

El hombre de paja

Necesito saber cuántas veces se ha hecho clic en un botón y hacer algo con cada tercer clic ...

Solución bastante obvia

// Declare counter outside event handler's scope
var counter = 0;
var element = document.getElementById('button');

element.addEventListener("click", function() {
  // Increment outside counter
  counter++;

  if (counter === 3) {
    // Do something every third time
    console.log("Third time's the charm!");

    // Reset counter
    counter = 0;
  }
});
<button id="button">Click Me!</button>

Ahora, esto funcionará, pero invade el alcance externo al agregar una variable, cuyo único propósito es realizar un seguimiento del recuento. En algunas situaciones, esto sería preferible ya que su aplicación externa podría necesitar acceso a esta información. Pero en este caso, solo cambiamos el comportamiento de cada tercer clic, por lo que es preferible incluir esta funcionalidad dentro del controlador de eventos .

Considere esta opción

var element = document.getElementById('button');

element.addEventListener("click", (function() {
  // init the count to 0
  var count = 0;

  return function(e) { // <- This function becomes the click handler
    count++; //    and will retain access to the above `count`

    if (count === 3) {
      // Do something every third time
      console.log("Third time's the charm!");

      //Reset counter
      count = 0;
    }
  };
})());
<button id="button">Click Me!</button>

Note algunas cosas aquí.

En el ejemplo anterior, estoy usando el comportamiento de cierre de JavaScript. Este comportamiento permite que cualquier función tenga acceso al ámbito en el que fue creada, de forma indefinida. Para aplicar esto de manera práctica, inmediatamente invoco una función que devuelve otra función, y debido a que la función que estoy devolviendo tiene acceso a la variable de recuento interno (debido al comportamiento de cierre explicado anteriormente), esto da como resultado un ámbito privado para el uso del resultado función ... ¿No es tan simple? Vamos a diluirlo ...

Un simple cierre de una línea

//          _______________________Immediately invoked______________________
//         |                                                                |
//         |        Scope retained for use      ___Returned as the____      |
//         |       only by returned function   |    value of func     |     |
//         |             |            |        |                      |     |
//         v             v            v        v                      v     v
var func = (function() { var a = 'val'; return function() { alert(a); }; })();

Todas las variables fuera de la función devuelta están disponibles para la función devuelta, pero no están disponibles directamente para el objeto de función devuelto ...

func();  // Alerts "val"
func.a;  // Undefined

¿Consíguelo? Entonces, en nuestro ejemplo principal, la variable de recuento está contenida dentro del cierre y siempre disponible para el controlador de eventos, por lo que conserva su estado de clic a clic.

Además, este estado de variable privada es totalmente accesible, tanto para lecturas como para asignación a sus variables de ámbito privadas.

Ahí tienes; ahora está encapsulando completamente este comportamiento.

Publicación de blog completa (incluidas las consideraciones de jQuery)

0
522

Los cierres son difíciles de explicar porque se utilizan para hacer que funcione un comportamiento que todo el mundo espera intuitivamente que funcione de todos modos. Creo que la mejor manera de explicar ellos (y la forma en que me enteré de lo que hacen) es imaginar la situación sin ellos:

const makePlus = function(x) {
    return function(y) { return x + y; };
}

const plus5 = makePlus(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 el cuerpo de su 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 plus5 llevar su alcance (o más bien, el alcance de su padre). De esta manera, xestá bien definido y está ligado al valor 5.

1
  • "Se utilizan para hacer funcionar un comportamiento que todo el mundo espera intuitivamente que funcione de todos modos". Aprecio este comentario, ya que eso era en parte con lo que estaba luchando. Sentí que me estaba perdiendo algo, ¡pero resulta que no lo estaba!
    Shane
    10 jul.20 a las 18:45
402

TLDR

Un cierre es un vínculo entre una función y su entorno léxico externo (es decir, tal como está escrito), de modo que los identificadores (variables, parámetros, declaraciones de función, etc.) definidos dentro de ese entorno son visibles desde dentro de la función, independientemente de cuándo o desde donde se invoca la función.

Detalles

En la terminología de la especificación ECMAScript, se puede decir que un cierre se implementa mediante la [[Environment]]referencia de cada función-objeto, que apunta al entorno léxico dentro del cual se define la función.

Cuando se invoca una función a través del [[Call]]método interno , la [[Environment]]referencia en el objeto de función se copia en la referencia del entorno externo del registro de entorno del contexto de ejecución recién creado (marco de pila).

En el siguiente ejemplo, la función se fcierra sobre el entorno léxico del contexto de ejecución global:

function f() {}

En el siguiente ejemplo, la función se hcierra sobre el entorno léxico de la función g, que, a su vez, se cierra sobre el entorno léxico del contexto de ejecución global.

function g() {
    function h() {}
}

Si una función interna es devuelta por una externa, entonces el entorno léxico externo persistirá después de que la función externa haya regresado. Esto se debe a que el entorno léxico externo debe estar disponible si finalmente se invoca la función interna.

En el siguiente ejemplo, la función se jcierra sobre el entorno léxico de la función i, lo que significa que la variable xes visible desde el interior de la función j, mucho después de que la función ihaya completado la ejecución:

function i() {
    var x = 'mochacchino'
    return function j() {
        console.log('Printing the value of x, from within function j: ', x)
    }
} 

const k = i()
setTimeout(k, 500) // invoke k (which is j) after 500ms

En un cierre, las variables en el entorno léxico externo en sí mismas están disponibles, no copias.

function l() {
  var y = 'vanilla';

  return {
    setY: function(value) {
      y = value;
    },
    logY: function(value) {
      console.log('The value of y is: ', y);
    }
  }
}

const o = l()
o.logY() // The value of y is: vanilla
o.setY('chocolate')
o.logY() // The value of y is: chocolate

La cadena de entornos léxicos, vinculados entre contextos de ejecución a través de referencias de entornos externos, forma una cadena de alcance y define los identificadores visibles desde cualquier función dada.

Tenga en cuenta que, en un intento por mejorar la claridad y la precisión, esta respuesta se ha modificado sustancialmente con respecto al original.

0
392

De acuerdo, fan de los cierres de 6 años. ¿Quieres escuchar el ejemplo más simple de cierre?

Imaginemos la siguiente situación: un conductor está sentado en un automóvil. Ese auto está dentro de un avión. El avión está en el aeropuerto. La capacidad del conductor para acceder a cosas fuera de su automóvil, pero dentro del avión, incluso si ese avión sale de un aeropuerto, es un cierre. Eso es todo. Cuando cumpla 27 años, mire la explicación más detallada o el ejemplo a continuación.

Así es como puedo convertir la historia de mi avión en el código.

var plane = function(defaultAirport) {

  var lastAirportLeft = defaultAirport;

  var car = {
    driver: {
      startAccessPlaneInfo: function() {
        setInterval(function() {
          console.log("Last airport was " + lastAirportLeft);
        }, 2000);
      }
    }
  };
  car.driver.startAccessPlaneInfo();

  return {
    leaveTheAirport: function(airPortName) {
      lastAirportLeft = airPortName;
    }
  }
}("Boryspil International Airport");

plane.leaveTheAirport("John F. Kennedy");
0
374

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

  • Un cierre no solo se crea cuando devuelve una función interna. De hecho, no es necesario que la función adjunta regrese para que se cree 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 donde podría llamarse inmediatamente o en cualquier momento posterior. Por lo tanto, el cierre de la función envolvente probablemente se cree tan pronto como se llame a la función envolvente, ya que cualquier función interior tiene acceso a ese cierre siempre que se llame a la función interior, antes o después de que regrese la función envolvente.
  • 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 que se ve 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 se llama a 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 las variables de su cierre contenedor, hasta el alcance global.
  • Los cierres usan memoria, pero no causan pérdidas de memoria, ya que JavaScript limpia por sí mismo sus propias estructuras circulares a las que no se hace referencia. Las pérdidas de memoria de Internet Explorer que involucran cierres se crean cuando no puede desconectar los valores de atributo DOM que hacen referencia a cierres, manteniendo así referencias a estructuras posiblemente circulares.
0
243

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

Closures are a way to let a function have persistent, private variables - that is, variables that only one function knows about, where it can keep track of info from previous times that it was run.

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 cositas de cierre?

0
219

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 las funciones addy de esa calculadora multiplymucho después de los make_calculatorretornos.

Si está familiarizado con los marcos 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 usa "marcos de montón", que pueden persistir después de la llamada a la función que los hizo regresar.

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

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



* Por ejemplo, cubre todos los puntos en el artículo "Cierres para Dummies" dado en otra respuesta , excepto el ejemplo 6, que simplemente muestra que las variables se pueden usar antes de que se declaren, un dato agradable de saber pero que no tiene ninguna relación con los cierres. También cubre todos los puntos en la respuesta aceptada , excepto los puntos (1) que las funciones copian sus argumentos en variables locales (los argumentos de la función nombrada), y (2) que copiar números crea un nuevo número, pero copiar una referencia de objeto le da otra referencia al mismo objeto. También es bueno saberlos, pero tampoco están relacionados con los cierres. También es muy similar al ejemplo de 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 corrientevalor 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 encierra su función interna y se invoca 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 nada conectado). Nuevamente, muy útil cuando se crean cierres, pero no forma 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 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 "conectar") que es 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 varias están anidadas, o incluso en el contexto global, como esta respuesta señala claramente.

0
210

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 hogar? Cuando una mamá tiene un hijo, el niño realmente no posee nada, ¿verdad? Pero sus padres son dueños de una casa, por lo que cuando 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 siempre (incluso si está en el extranjero) poder decir que tiene un hogar, aunque en realidad son los padres los dueños de la casa.

0
207

¿Puede explicarle los cierres a un niño de 5 años? *

Sigo pensando 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);​

Prueba de que este ejemplo crea un cierre incluso si la función interna no regresa

* Pregunta AC #

0
182

Tiendo a aprender mejor mediante comparaciones BUENAS / MALAS. Me gusta ver el código que funciona seguido de un código que no funciona con el que es probable que alguien se encuentre. Monté un jsFiddle que hace una comparación y trata de reducirse las diferencias a las explicaciones más simples que pudiera ocurrir.

Cierres bien hechos:

console.log('CLOSURES DONE RIGHT');

var arr = [];

function createClosure(n) {
    return function () {
        return 'n = ' + n;
    }
}

for (var index = 0; index < 10; index++) {
    arr[index] = createClosure(index);
}

for (var index in arr) {
    console.log(arr[index]());
}
  • En el código anterior createClosure(n)se invoca en cada iteración del bucle. Tenga en cuenta que nombré la variable npara resaltar que es una nueva variable creada en un nuevo alcance de función y no es la misma variable indexque está vinculada al alcance externo.

  • Esto crea un nuevo alcance y nestá vinculado a ese alcance; esto significa que tenemos 10 ámbitos separados, uno para cada iteración.

  • createClosure(n) devuelve una función que devuelve la n dentro de ese ámbito.

  • Dentro de cada ámbito nestá vinculado a cualquier valor que tenía cuando createClosure(n)se invocó, por lo que la función anidada que se devuelve siempre devolverá el valor nque tenía cuando createClosure(n)se invocó.

Cierres mal hechos:

console.log('CLOSURES DONE WRONG');

function createClosureArray() {
    var badArr = [];

    for (var index = 0; index < 10; index++) {
        badArr[index] = function () {
            return 'n = ' + index;
        };
    }
    return badArr;
}

var badArr = createClosureArray();

for (var index in badArr) {
    console.log(badArr[index]());
}
  • En el código anterior, el bucle se movió dentro de la createClosureArray()función y la función ahora solo devuelve la matriz completa, que a primera vista parece más intuitiva.

  • Lo que podría no ser obvio es que ya que createClosureArray()solo se invoca una vez que solo se crea un alcance para esta función en lugar de uno para cada iteración del ciclo.

  • Dentro de esta función indexse define una variable nombrada . El ciclo se ejecuta y agrega funciones a la matriz que regresan index. Tenga en cuenta que indexse define dentro de la createClosureArrayfunción que solo se invoca una vez.

  • Debido a que solo había un alcance dentro de la createClosureArray()función, indexsolo está vinculado a un valor dentro de ese alcance. En otras palabras, cada vez que el bucle cambia el valor de index, lo cambia para todo lo que hace referencia a él dentro de ese alcance.

  • Todas las funciones agregadas a la matriz devuelven la MISMA indexvariable del ámbito principal donde se definió en lugar de 10 diferentes de 10 ámbitos diferentes como el primer ejemplo. El resultado final es que las 10 funciones devuelven la misma variable del mismo ámbito.

  • Una vez que el ciclo terminó y indexse modificó, el valor final fue 10, por lo tanto, cada función agregada a la matriz devuelve el valor de la indexvariable única que ahora está establecida en 10.

Resultado

CLOSURES DONE RIGHT
n = 0
n = 1
n = 2
n = 3
n = 4
n = 5
n = 6
n = 7
n = 8
n = 9

CLOSURES DONE WRONG
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10

0
168

Wikipedia sobre cierres :

In computer science, a closure is a function together with a referencing environment for the nonlocal names (free variables) of that function.

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

Dado que la construcción que define el alcance en JavaScript es una función , no un bloque de código como en muchos otros lenguajes, lo que generalmente queremos decir con cierre en JavaScript es una función que trabaja con variables no locales definidas en una función circundante ya ejecutada .

Los cierres se utilizan a menudo 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 usa una función anónima, que se ejecutó una vez. Pero no tiene por qué serlo. Se puede nombrar (por ejemplo mkdb) y ejecutar 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.

0
138

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

Este es 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
0
133

The children will always remember the secrets they have shared with their parents, even after their parents are gone. This is what closures are for functions.

Los secretos de las funciones de JavaScript son las variables privadas

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

Cada vez que lo llama, se crea la variable local "nombre" 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 adivinar, debido a que las variables se vuelven a crear 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 existe esta cosa muy especial de que las funciones que se crean dentro de otras funciones, también pueden conocer las variables locales de sus padres y conservarlas 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 comparten las variables secretas del lugar secreto.

Pero lo triste es que si el hijo también es una variable privada de su función padre, también moriría cuando el padre termina, y los secretos morirían con ellos.

Entonces, 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", el recuerdo de ella no se pierde y su hijo 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.

0
107

No entiendo por qué las respuestas son tan complejas aquí.

Aquí hay un cierre:

var a = 42;

function b() { return a; }

Si. Probablemente lo use muchas veces al día.


There is no reason to believe closures are a complex design hack to address specific problems. No, closures are just about using a variable that comes from a higher scope from the perspective of where the function was declared (not run).

Now what it allows you to do can be more spectacular, see other answers.

0
95

Ejemplo para el primer punto de dlaliberte:

A closure is not only created when you return an inner function. In fact, the enclosing function does not need to return at all. You might instead assign your inner function to a variable in an outer scope, or pass it as an argument to another function where it could be used immediately. Therefore, the closure of the enclosing function probably already exists at the time that enclosing function was called since any inner function has access to it as soon as it is called.

var i;
function foo(x) {
    var tmp = 3;
    i = function (y) {
        console.log(x + y + (++tmp));
    }
}
foo(2);
i(3);
0
93

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.

0
87

Sé que ya hay muchas soluciones, pero supongo que este pequeño y sencillo 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
0
84

Estás durmiendo e invitas a Dan. Le dices a Dan que traiga un mando de Xbox.

Dan invita a Paul. Dan le pide a Paul que traiga un controlador. ¿Cuántos controladores se trajeron 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.");
0
83

El autor de Closures ha explicado bastante bien los cierres, explicando la razón por la que los necesitamos y también explicando el entorno léxico, que es necesario para comprender los cierres.
Aquí está el resumen:

¿Qué pasa si se accede a una variable, pero no es local? Como aquí:

Ingrese la descripción de la imagen aquí

En este caso, el intérprete encuentra la variable en el LexicalEnvironmentobjeto externo .

El proceso consta de dos pasos:

  1. Primero, cuando se crea una función f, no se crea en un espacio vacío. Hay un objeto LexicalEnvironment actual. En el caso anterior, su ventana (a no está definida en el momento de la creación de la función).

Ingrese la descripción de la imagen aquí

Cuando se crea una función, obtiene una propiedad oculta, denominada [[Scope]], que hace referencia al entorno léxico actual.

Ingrese la descripción de la imagen aquí

Si se lee una variable, pero no se encuentra en ningún lugar, se genera un error.

Funciones anidadas

Las funciones se pueden anidar unas dentro de otras, formando una cadena de entornos léxicos que también se pueden llamar una cadena de alcance.

Ingrese la descripción de la imagen aquí

Entonces, la función g tiene acceso a g, ay f.

Cierres

Una función anidada puede continuar viva después de que la función externa haya finalizado:

Ingrese la descripción de la imagen aquí

Marcado de entornos léxicos:

Ingrese la descripción de la imagen aquí

Como vemos, this.sayes una propiedad en el objeto de usuario, por lo que continúa vivo después de que Usuario haya completado.

Y si recuerda, cuando this.sayse crea, (como todas las funciones) obtiene una referencia interna this.say.[[Scope]]al LexicalEnvironment actual. Por lo tanto, el entorno léxico de la ejecución del usuario actual permanece en la memoria. Todas las variables de Usuario también son sus propiedades, por lo que también se guardan cuidadosamente, no se desechan como de costumbre.

El objetivo es asegurarse de que si la función interna quiere acceder a una variable externa en el futuro, pueda hacerlo.

Para resumir:

  1. La función interna mantiene una referencia al entorno léxico externo.
  2. La función interna puede acceder a variables desde ella en cualquier momento, incluso si la función externa está terminada.
  3. El navegador mantiene el entorno léxico y todas sus propiedades (variables) en la memoria hasta que haya una función interna que haga referencia a él.

A esto se le llama cierre.

77

Las funciones de JavaScript pueden acceder a sus:

  1. Argumentos
  2. Locales (es decir, sus variables locales y funciones locales)
  3. Medio ambiente, que incluye:
    • globales, incluido 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 ofrecen beneficios que no discuto aquí. Al acceder a los datos en su entorno, un cierre mantiene vivos esos datos. En el sub caso 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 sale, entonces la función interna conserva los datos locales de la función externa. viva.

Ejemplo de un cierre que utiliza el entorno global:

Imagine que los eventos del botón Stack Overflow Vote-Up y Vote-Down se implementan como cierres, voteUp_click y voteDown_click, que tienen acceso a las variables externas isVotedUp y isVotedDown, que se definen globalmente. (En aras de la simplicidad, me refiero a los botones de voto de preguntas de StackOverflow, no al conjunto de botones de voto de respuesta).

Cuando el usuario hace clic en el botón VoteUp, la función voteUp_click comprueba si isVotedDown == true para determinar si votar a favor o simplemente cancelar un voto en contra. 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 funciones son cierres, ya que todas acceden a su entorno.

0
59

Como padre de un niño de 6 años, que actualmente enseña a niños pequeños (y un novato relativo a la codificación sin educación formal, por lo que se requerirán 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 tiene la edad suficiente para intentarlo ellos mismos. 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 se puede tocar, oler o saborear. Puede escribirlo, hablarlo y escucharlo. Podrías usarlo para crear un toque, olor y sabor usando una computadora. Una computadora puede hacer que sea útil usando código.

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

JAVASCRIPT: JavaScript es un idioma. Como el inglés, el francés o el chino son idiomas. Hay muchos lenguajes que entienden las computadoras y otros procesadores electrónicos. Para que JavaScript sea entendido por una computadora, necesita un intérprete. Imagínese si un profesor que solo habla ruso viene a dar su clase en la escuela. Cuando el profesor dice "все садятся", la clase no lo entiende. Pero afortunadamente tienes un alumno ruso en tu clase que les dice a todos que esto significa "todos siéntense", 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 conocer 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 denominan funciones.

FUNCIÓN: Una función en JavaScript es como una fábrica. Podría ser una pequeña fábrica con una sola máquina adentro. 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 entren resmas de tela y bobinas de hilo y salgan camisetas y jeans. Nuestra fábrica de JavaScript solo procesa datos, no puede coser, perforar un agujero o derretir metal. En nuestra fábrica de JavaScript entran y salen datos.

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é preparar para la cena. Digamos que los invito a usted y a su amigo a mi casa. A ti más te gustan los muslos de pollo, a mí me gustan las salchichas, tu amiga siempre quiere lo que tú quieres y mi amiga 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 caliente al mismo tiempo. Necesitamos proporcionar a la función 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. Como esto:

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

Tenga en cuenta eso /*...*/y //deje de leer el código en 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 a la segunda 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: "Paréntesis" o ()son el buzón en la puerta de la fábrica de funciones 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 sabrá qué datos tiene que proporcionar.

TIRANTES: "Frenos" que tienen este aspecto {}son los cristales tintados de nuestra fábrica. Desde el interior de la fábrica se puede ver el exterior, pero desde el exterior no se puede ver el interior.

EL EJEMPLO DE CÓDIGO LARGO ARRIBA

Nuestro código comienza con la palabra función , ¡así que sabemos que es una! Luego, el nombre de la función canta , esa es mi propia descripción de lo que trata la función. Luego paréntesis () . Los paréntesis siempre están 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 un aparato ortopédico como este {. Esto marca el inicio de la función sing () . Tiene un compañero que marca el final de cantar () así}

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

Entonces, esta función podría tener algo que ver con el canto y podría necesitar algunos datos sobre una persona. Tiene instrucciones adentro 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 significan "variable". Una variable es como un sobre. En el exterior, este sobre está marcado como "persona". En su interior contiene un trozo de papel con la información que necesita nuestra función, unas letras y espacios unidos como un trozo de cuerda (se llama cuerda) que forman una frase que dice "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 debido a que 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. A esto lo llamamos una 'variable global'.

VARIABLE GLOBAL: persona es una variable global, lo que significa que si cambias su valor de "una anciana" a "un joven", la persona seguirá siendo un joven hasta que decidas cambiarla 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 de 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.

"Come on sing, Come and get person!"

Cuando el navegador haya cargado el código JavaScript y haya llegado a esta línea, 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 acciones; la función principal es cantar. Contiene una variable llamada firstPart que se aplica al canto sobre la persona que se aplica a cada uno de los versos de la canción: "Hubo" + persona + "que se 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 propios frenos, lo que significa que las variables dentro de ellos no se pueden ver desde el exterior. Por eso los nombres de las variables ( criatura y resultado ) se pueden repetir en los cierres pero con valores diferentes. Si escribe estos nombres de variables en la ventana de la consola, no obtendrá su valor porque está oculto por dos capas de ventanas tintadas.

Todos los cierres saben cuál 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. Entonces se realizará el trabajo de la función sing ().

0
57

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

Imagine - you are playing with your little brothers and sisters in the entire house, and you are moving around with your toys and brought some of them into your older brother's room. After a while your brother returned from the school and went to his room, and he locked inside it, so now you could not access toys left there anymore in a direct way. But you could knock the door and ask your brother for that toys. This is called toy's closure; your brother made it up for you, and he is now into outer scope.

Compare con una situación en la que una puerta se cerró con un tiro y nadie adentro (ejecución de la función general), y luego se produjo un incendio local y quemó 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 se dejaron en la primera instancia de la habitación.

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 todavía 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.

50

Una respuesta para un niño de seis años (suponiendo que sepa 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 de 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 cambio, esa función principal "se cierra". En otras palabras, nada puede mirar dentro de él y ver las variables que usó, 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 sencilla de explicarlo es en términos de alcance:

Cada vez que crea un alcance más pequeño dentro de un alcance más grande, el alcance más pequeño siempre podrá ver lo que está en el alcance más grande.

0
50

Una función en JavaScript no es solo una referencia a un conjunto de instrucciones (como en el lenguaje C), sino que también incluye una estructura de datos oculta que se compone de referencias a todas las variables no locales que utiliza (variables capturadas). Estas funciones de dos piezas se denominan cierres. Cada función en JavaScript puede considerarse un cierre.

Los cierres son funciones con un estado. Es algo similar a "esto" en el sentido de que "esto" también proporciona el estado de una función, pero la función y "esto" son objetos separados ("esto" es solo un parámetro elegante, y la única forma de vincularlo permanentemente a un función es crear un cierre). Si bien "esto" y la función siempre viven por separado, una función no puede separarse de su cierre y el lenguaje no proporciona ningún medio para acceder a las variables capturadas.

Debido a que todas estas variables externas a las que hace referencia una función anidada léxicamente son en realidad variables locales en la cadena de sus funciones que las encierran léxicamente (se puede suponer que las variables globales son variables locales de alguna función raíz), y cada ejecución de una función crea nuevas instancias de sus variables locales, se deduce que cada ejecución de una función que devuelve (o transfiere de otro modo, como registrarla como una devolución de llamada) una función anidada crea un nuevo cierre (con su propio conjunto potencialmente único de variables no locales referenciadas que representan su ejecución contexto).

Además, debe entenderse que las variables locales en JavaScript no se crean en el marco de la pila, sino en el montón y se destruyen solo cuando nadie hace referencia a ellas. Cuando una función regresa, las referencias a sus variables locales se reducen, pero aún pueden ser no nulas si durante la ejecución actual se convirtieron en parte de un cierre y siguen siendo referenciadas por sus funciones léxicamente anidadas (lo que puede suceder solo si las referencias a estas funciones anidadas fueron devueltas o transferidas a algún código externo).

Un ejemplo:

function foo (initValue) {
   //This variable is not destroyed when the foo function exits.
   //It is 'captured' by the two nested functions returned below.
   var value = initValue;

   //Note that the two returned functions are created right now.
   //If the foo function is called again, it will return
   //new functions referencing a different 'value' variable.
   return {
       getValue: function () { return value; },
       setValue: function (newValue) { value = newValue; }
   }
}

function bar () {
    //foo sets its local variable 'value' to 5 and returns an object with
    //two functions still referencing that local variable
    var obj = foo(5);

    //Extracting functions just to show that no 'this' is involved here
    var getValue = obj.getValue;
    var setValue = obj.setValue;

    alert(getValue()); //Displays 5
    setValue(10);
    alert(getValue()); //Displays 10

    //At this point getValue and setValue functions are destroyed
    //(in reality they are destroyed at the next iteration of the garbage collector).
    //The local variable 'value' in the foo is no longer referenced by
    //anything and is destroyed too.
}

bar();
49

Quizás un poco más allá de todos, excepto el más precoz de los niños de seis años, pero algunos ejemplos que ayudaron a hacer que el concepto de cierre en JavaScript me hiciera clic.

Un cierre es una función que tiene acceso al alcance de otra función (sus variables y funciones). La forma más sencilla de crear un cierre es con una función dentro de una función; la razón es que en JavaScript una función siempre tiene acceso al alcance de su función contenedora.

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        alert(outerVar);
    }
    
    innerFunction();
}

outerFunction();

ALERTA: mono

En el ejemplo anterior, se llama a externalFunction, que a su vez llama a innerFunction. Observe cómo externalVar está disponible para innerFunction, evidenciado por su alerta correcta del valor de outerVar.

Ahora considere lo siguiente:

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        return outerVar;
    }
    
    return innerFunction;
}

var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());

ALERTA: mono

referenceToInnerFunction se establece en externalFunction (), que simplemente devuelve una referencia a innerFunction. Cuando se llama a referenceToInnerFunction, devuelve externalVar. Nuevamente, como arriba, esto demuestra que innerFunction tiene acceso a outerVar, una variable de externalFunction. Además, es interesante notar que retiene este acceso incluso después de que la función externa ha terminado de ejecutarse.

Y aquí es donde las cosas se ponen realmente interesantes. Si tuviéramos que deshacernos de externalFunction, digamos que lo establezcamos en nulo, podría pensar que referenceToInnerFunction perdería su acceso al valor de outerVar. Pero este no es el caso.

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        return outerVar;
    }
    
    return innerFunction;
}

var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());

outerFunction = null;
alert(referenceToInnerFunction());

ALERTA: mono ALERTA: mono

Pero, ¿cómo es esto así? ¿Cómo puede referenceToInnerFunction aún conocer el valor de outerVar ahora que externalFunction se ha establecido en nulo?

La razón por la que referenceToInnerFunction todavía puede acceder al valor de outerVar es porque cuando el cierre se creó por primera vez colocando innerFunction dentro de outsideFunction, innerFunction agregó una referencia al alcance de externalFunction (sus variables y funciones) a su cadena de alcance. Lo que esto significa es que innerFunction tiene un puntero o referencia a todas las variables de externalFunction, incluida externalVar. Entonces, incluso cuando la función externa ha terminado de ejecutarse, o incluso si se elimina o se establece en nulo, las variables en su alcance, como la variable externa, se quedan en la memoria debido a la excelente referencia a ellas por parte de la función interna que se ha devuelto a referenceToInnerFunction. Para liberar de verdad la variable OuterVar y el resto de las variables de la función externa de la memoria, tendría que deshacerse de esta excelente referencia a ellas,digamos estableciendo referenceToInnerFunction en nulo también.

//////////

Otras dos cosas sobre los cierres a tener en cuenta. Primero, el cierre siempre tendrá acceso a los últimos valores de su función contenedora.

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        alert(outerVar);
    }
    
    outerVar = "gorilla";

    innerFunction();
}

outerFunction();

ALERTA: gorila

En segundo lugar, cuando se crea un cierre, conserva una referencia a todas las variables y funciones de su función circundante; no puede escoger y elegir. Y, sin embargo, los cierres deben usarse con moderación, o al menos con cuidado, ya que pueden consumir mucha memoria; muchas variables se pueden mantener en la memoria mucho después de que una función contenedora haya terminado de ejecutarse.

0
46

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 del cierre y el uso práctico que he encontrado. Se recomienda encarecidamente a cualquiera que esté aprendiendo JavaScript.

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

0