function que closure - ¿Cómo funcionan los cierres de JavaScript?




15 Answers

Cuando vea la palabra clave de función dentro de otra función, la función interna tendrá acceso a las variables en la función externa.

function foo(x) {
  var tmp = 3;

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

  bar(10);
}

foo(2);

Esto siempre registrará 16, porque la bar puede acceder a la x que se definió como un argumento para foo , y también puede acceder a tmp desde foo .

Eso es un cierre. Una función no tiene que volver para que se llame cierre. Simplemente accediendo a variables fuera de su alcance léxico inmediato crea 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 is now a closure.
bar(10);

La función anterior también registrará 16, porque la bar todavía puede referirse a x y tmp , aunque ya no esté directamente dentro del alcance.

Sin embargo, dado que tmp todavía está dando vueltas dentro del cierre de la bar , también se está incrementando. Se incrementará cada vez que llames 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, se crea un nuevo contexto de ejecución. Junto con los argumentos de la función y el objeto principal, este contexto de ejecución también recibe todas las variables declaradas fuera de él (en el ejemplo anterior, tanto 'a' como 'b').

Es posible crear más de una función de cierre, ya sea devolviendo una lista de ellas o configurándolas en variables globales. Todos estos se referirán al mismo x y al mismo tmp , no hacen sus propias copias.

Aquí el número x es un número literal. Al igual que con otros literales en JavaScript, cuando se llama foo , el número x se copia en foo como su argumento x .

Por otro lado, JavaScript siempre usa referencias cuando se trata de objetos. Si, por ejemplo, foo a foo con un objeto, el cierre que devuelve hará referencia al objeto original.

function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + tmp);
    x.memb = x.memb ? x.memb + 1 : 1;
    console.log(x.memb);
  }
}

var age = new Number(2);
var bar = foo(age); // bar is now a closure referencing age.
bar(10);

Como se esperaba, cada llamada a la bar(10) incrementará x.memb . Lo que podría no esperarse es que x simplemente se refiere al mismo objeto que la variable de age . Después de un par de llamadas a la bar , age.memb será 2! Esta referencia es la base para las fugas de memoria con objetos HTML.

en español programacion

¿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ó.




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



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.




OK, fan de 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 carro 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.Cuando cumpla 27 años, observe la explicación más detallada o el ejemplo a continuación.

Aquí es cómo puedo convertir mi historia 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");




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?




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.




Tiendo a aprender mejor por comparaciones BUENO / MALO. Me gusta ver el código de trabajo seguido del código que no funciona y que es probable que encuentre alguien. 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 ámbito de función y no es la misma variable indexque está vinculada al ámbito 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 alcance.

  • Dentro de cada ámbito nestá vinculado al 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 ámbito para esta función en lugar de uno para cada iteración del bucle.

  • Dentro de esta función indexse define una variable nombrada . El bucle se ejecuta y agrega funciones a la matriz que retorna 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 dentro de ese ámbito.

  • 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 desde el mismo ámbito.

  • Una vez que el bucle terminó y indexse modificó, el valor final era 10, por lo tanto, cada función agregada a la matriz devuelve el valor de la indexvariable única que ahora se establece en 10.

Resultado

CIERRES REALIZADOS A LA DERECHA
n = 0
n = 1
n = 2
n = 3
n = 4
n = 5
n = 6
n = 7
n = 8
n = 9

CIERRES REALIZADOS INCORRECTO
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10




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



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

Aquí hay un cierre:

var a = 42;

function b() { return a; }

Sí. Probablemente lo uses muchas veces al día.


No hay razón para creer que los cierres son un truco de diseño complejo para abordar problemas específicos. No, los cierres se tratan simplemente de usar una variable que proviene de un alcance superior desde la perspectiva de dónde se declaró la función (no se ejecutó) .

Ahora lo que te permite hacer puede ser más espectacular, ver otras respuestas.




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.




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.");



El autor de Closures ha explicado los cierres bastante bien, 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í:

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 actual LexicalEnvironment. En el caso anterior, su ventana (a no está definida en el momento de la creación de la función).

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

Si se lee una variable, pero no se puede encontrar en ninguna parte, se genera un error.

Funciones anidadas

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

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

Cierres

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

Marcando los entornos léxicos:

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

Y si recuerda, cuando this.sayse crea, (como todas las funciones) obtiene una referencia interna this.say.[[Scope]]al entorno léxico 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 garantizar que si la función interna desea acceder a una variable externa en el futuro, puede hacerlo.

Para resumir:

  1. La función interna mantiene una referencia al entorno léxico externo.
  2. La función interna puede acceder a las variables desde 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 lo referencia.

Esto se llama un cierre.




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.




Una función en JavaScript no es solo una referencia a un conjunto de instrucciones (como en 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). Tales funciones de dos piezas se llaman cierres. Cada función en JavaScript puede considerarse un cierre.

Los cierres son funciones con un estado. Es algo similar a "this" en el sentido de que "this" también proporciona un estado para una función, pero "function" y "this" son objetos separados ("this" es solo un parámetro elegante, y la única forma de vincularlo permanentemente a un La 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 envolventes léxicas (se puede suponer que las variables globales son variables locales de alguna función raíz), y cada ejecución individual de una función crea nuevas instancias de En sus variables locales, se deduce que cada ejecución de una función que devuelve (o la transfiere de otra manera, 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 las está haciendo referencia. 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 aún son referenciadas por sus funciones anidadas léxicamente (lo que puede suceder solo si las referencias a estas funciones anidadas se devolvieron o se transfirieron 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();



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.






Related