Pregunta ¿Cómo funcionan los cierres de JavaScript?


¿Cómo explicarías el cierre de JavaScript a alguien que conozca los conceptos en los que se basan (por ejemplo, funciones, variables, etc.), pero que no comprenda los cierres por sí mismos?

he visto el ejemplo del esquema dado en Wikipedia, pero desafortunadamente no ayudó.


7654


origen


Respuestas:


Cierres 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 código JavaScript que funcione. No es para gurús o programadores funcionales.

Los cierres son no dificil para entender una vez que el concepto central se haya asimilado. Sin embargo, son imposibles de entender al leer cualquier documento académico o información académica sobre ellos.

Este artículo está dirigido a programadores con 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');

Un ejemplo de un cierre

Dos resúmenes de oraciones:

  • 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, pasar como un argumento a una función o ser devuelto como resultado de una función.

  • O bien, un cierre es un marco de pila que se asigna cuando una función comienza su ejecución, y no liberado después de que la función retorna (¡como si un 'marco de pila' estuviera asignado en el montón en lugar de la pila!).

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 comprenderán cómo se devuelve una referencia a una función a una variable (say2) en el código anterior. Si no lo hace, entonces debe ver eso antes de que pueda conocer los cierres. Un programador que usa C pensaría que la función devuelve un puntero a una función, y que las variables say y say2 cada uno era un puntero a una función.

Existe una diferencia fundamental entre un puntero C a una función y una referencia de JavaScript a una función. En JavaScript, puede pensar en una variable de referencia de función como tener un puntero a una función tambié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); } es declarado dentro otra función, sayHello2() en este ejemplo. En JavaScript, si usas el function palabra clave dentro de otra función, está creando un cierre.

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

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

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

Mirando el resultado de say2.toString(), podemos ver que el código se refiere a la variable text. La función anónima puede hacer referencia text que tiene el valor 'Hello Bob' porque las variables locales de sayHello2() se mantienen en un cierre.

La magia es que en JavaScript una referencia de función también tiene una referencia secreta al cierre en el que se creó, similar a cómo los delegados son un puntero de método más una referencia secreta a un objeto.

Más ejemplos

Por alguna razón, los cierres parecen realmente difíciles de entender cuando lees sobre ellos, pero cuando ves algunos ejemplos queda claro cómo funcionan (me tomó un tiempo). Recomiendo analizar cuidadosamente los ejemplos hasta que comprenda cómo funcionan. Si comienzas a utilizar 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, sino que se guardan por referencia. ¡Es como mantener un marco de pila en la memoria cuando sale 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 a mismo cierre porque todos están declarados 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 setupSomeGlobals() nuevamente, se crea un nuevo cierre (stack-frame!). El viejo gLogNumber, gIncreaseNumber, gSetNumber las variables se sobrescriben con nuevo funciones que tienen el nuevo cierre. (En JavaScript, cada vez que declara una función dentro de otra función, la (s) función (es) interior (es) se (n) vuelven a crear cada Hora en que se llama a la función externa.

Ejemplo 5

Este ejemplo muestra que el cierre contiene cualquier variable local que haya sido declarada dentro de la función externa antes de salir. Tenga en cuenta que la variable alice en realidad se declara 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 al alice variable porque alice está en el mismo ámbito (JavaScript lo hace elevación variable) también sayAlice()() simplemente llama directamente a la referencia de función devuelta desde sayAlice() - Es exactamente lo mismo que lo 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"

Tricky: tenga en cuenta también que el say la variable también está dentro del cierre, y podría accederse por cualquier otra función que pueda declararse dentro de sayAlice(), o se puede acceder recursivamente dentro de la función interna.

Ejemplo 6

Este es realmente un juego para mucha gente, así que debes entenderlo. Tenga mucho cuidado si está definiendo una función dentro de un ciclo: las variables locales del cierre pueden no actuar como podría pensar primero.

Debe entender 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 a la matriz de resultados. Si no está tan familiarizado con las funciones anónimas, piense en ello 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 se llaman las funciones anónimas en la línea fnlist[j](); todos usan el mismo cierre único, y usan el valor actual para i y item dentro de ese único cierre (donde i tiene un valor de 3 porque el ciclo se completó, y item tiene un valor de 'item2') Tenga en cuenta que estamos indexando desde 0 por lo tanto item tiene un valor de item2. Y el i ++ aumentará i al valor 3.

Puede ser útil ver qué sucede cuando una declaración de nivel de bloque de la variable item se usa (a través del let palabra clave) en lugar de una declaración de variable con ámbito de función a través del var palabra clave. Si se realiza ese cambio, entonces cada función anónima en el conjunto result 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 en lugar de var, entonces la salida es:

item0 1
item1 2
item2 3

Ejemplo 7

En este último ejemplo, cada llamada a la función principal crea un cierre por 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 confuso, lo mejor que se puede hacer es jugar con los ejemplos. Leer una explicación es mucho más difícil que comprender ejemplos. Mis explicaciones sobre cierres y marcos de pila, etc. no son técnicamente correctas, son simplificaciones brutales destinadas a ayudar a comprender. Una vez que se haya asimilado la idea básica, puede recoger los detalles más adelante.

Puntos finales:

  • Siempre que uses function dentro de otra función, se usa un cierre.
  • Siempre que uses 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 eval incluso puedes crear nuevas variables locales usando eval('var foo = …')
  • Cuando usas 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 estaban cuando se salía una función.
  • Probablemente sea mejor pensar que siempre se crea un cierre como 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 fuente, pero tienen un comportamiento completamente diferente debido a su cierre "oculto". No creo que el código JavaScript realmente pueda descubrir si una referencia de función tiene un cierre o no.
  • Si está intentando hacer modificaciones en el código fuente dinámico (por ejemplo: myFunction = Function(myFunction.toString().replace(/Hello/,'Hola'));), no funcionará si myFunction es un cierre (por supuesto, nunca se pensaría en hacer una sustitución de cadena de código fuente en tiempo de ejecución, pero ...).
  • Es posible obtener declaraciones de función dentro de las declaraciones de función dentro de las funciones, y puede obtener cierres en más de un nivel.
  • Normalmente creo que un cierre es un término tanto para la función como para 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 normalmente se encuentran en los lenguajes funcionales.

Campo de golf

Gracias

Si usted tiene sólo cierres aprendidos (¡aquí o en otro lugar!), entonces estoy interesado en recibir cualquier comentario suyo sobre los cambios que sugiera que podrían 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 Archivo de Internet.


6160



Cada vez que 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 bar puede acceder al x que se definió como un argumento para fooy también puede acceder tmp de foo.

Ese es un cierre. Una función no tiene por qué regreso para ser llamado un 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 bar todavía puede referirse a x y tmp, a pesar de que ya no está directamente dentro del alcance.

Sin embargo, desde tmp sigue dando vueltas dentro barestá cerrado, también se está incrementando. 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, se crea un nuevo contexto de ejecución. Junto con los argumentos de 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 estableciéndolas en variables globales. Todos estos se referirán a mismo  x y el mismo tmp, ellos no hacen sus propias copias.

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

Por otro lado, JavaScript siempre usa referencias cuando se trata de objetos. Si dijiste, llamaste foocon un objeto, el cierre que devuelve referencia ese 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 bar(10) aumentará x.memb. Lo que no se puede esperar, es que x simplemente se está refiriendo al mismo objeto que age ¡variable! Después de un par de llamadas a bar, age.memb será 2! Esta referencia es la base de las fugas de memoria con objetos HTML.


3804



PREFACIO: esta respuesta se escribió cuando la pregunta era:

Como dijo el viejo Albert: "Si no puedes explicárselo a un niño de seis años, realmente no lo entiendes tú mismo". Bueno, traté de explicar los cierres de JS a un amigo de 27 años y fallé por completo.

¿Alguien puede considerar que tengo 6 años y estoy extrañamente interesado en ese tema?

Estoy bastante seguro de que fui una de las únicas 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. Afortunadamente, la idea general de la historia sigue siendo divertida para algunos.


Soy un gran admirador de la analogía y la metáfora al explicar conceptos difíciles, así que déjame probar mi mano con una historia.

Había una vez:

Había una princesa ...

function princess() {

Ella vivía en un mundo maravilloso lleno de aventuras. Conoció a su Príncipe Encantador, cabalgó alrededor de su mundo en un unicornio, luchó con dragones, se encontró con animales parlanchines y muchas otras cosas fantásticas.

    var adventures = [];

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

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

    /* ... */

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

    return {

Y a menudo les contaba sobre su última increíble aventura como princesa.

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

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

var littleGirl = princess();

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

littleGirl.story();

Y a pesar de que los adultos sabían de princesas reales, nunca creerían en los unicornios o dragones porque nunca podrían verlos. Los adultos dijeron que solo existían dentro de la imaginación de la niña.

Pero sabemos la verdad real; que la niña con la princesa dentro ...

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


2251



Si tomamos la pregunta en serio, deberíamos descubrir qué es lo que un niño típico de 6 años es capaz de reconocer cognitivamente, aunque es cierto que alguien que está interesado en JavaScript no es tan típico.

En 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, de la siguiente manera:

La cocina es un cierre que tiene una variable local, llamada trashBags. Hay una función dentro de la cocina llamada getTrashBag eso 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();

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

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

  • Cada vez makeKitchen() se llama, se crea un nuevo cierre con su propio trashBags.
  • los trashBags variable es local al interior de cada cocina y no es accesible afuera, pero la función interna en el getTrashBagla propiedad tiene acceso a ella.
  • Cada llamada de función crea un cierre, pero no sería necesario mantener el cierre a menos que se pueda invocar una función interna, que tiene acceso al interior del cierre, desde el exterior del cierre. Devolviendo el objeto con el getTrashBag la función lo hace aquí.

691



El hombre de paja

Necesito saber cuántas veces se ha hecho clic en un botón y hacer algo en 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 ámbito 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 estamos cambiando el comportamiento de cada tercer clic, por lo que es preferible Adjunte esta funcionalidad dentro del controlador de eventos.

Considera 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>

Fíjate 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 se creó, indefinidamente. Para aplicarlo de manera práctica, invoco de inmediato una función que devuelve otra función y, como la función que estoy devolviendo tiene acceso a la variable de recuento interna (debido al comportamiento de cierre explicado anteriormente), esto da como resultado un ámbito privado para el uso 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 directamente disponibles para el objeto de función devuelto ...

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

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

Además, este estado de variable privado es completamente accesible, tanto para las lecturas como para la asignación a sus variables de ámbito privado.

Ahí vas; ahora estás encapsulando por completo este comportamiento.

Publicación completa del blog (incluyendo consideraciones de jQuery)


526



Los cierres son difíciles de explicar porque se usan para hacer funcionar algún comportamiento que, de forma intuitiva, todo el mundo espera que funcione. Encuentro la mejor manera de explicarlos (y la forma en que yo 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 lo hizo saber cierres? Simplemente reemplace la llamada en la última línea por su cuerpo de método (que es básicamente lo que hacen las funciones) y obtendrá:

console.log(x + 3);

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


444



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, la función adjunta no necesita regresar para nada 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 argumento a otra función donde podría llamarse inmediatamente o en cualquier momento posterior. Por lo tanto, probablemente se cree el cierre de la función adjunta tan pronto como se llame a la función adjunta 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 envolvente regrese.
  • Un cierre no hace referencia a una copia del viejos valores de variables en su alcance. Las variables en sí 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 cual 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 declarado dentro de la función. También incluyen argumentos de la función. Un cierre también tiene acceso a las variables que lo contienen, todo el camino hasta el alcance global.
  • Los cierres usan memoria, pero no causan pérdidas de memoria ya que JavaScript por sí solo limpia sus propias estructuras circulares que no están referenciadas. Las pérdidas de memoria de Internet Explorer que implican cierres se crean cuando no se pueden desconectar los valores de los atributos DOM que hacen referencia a los cierres, manteniendo así las referencias a estructuras posiblemente circulares.

342



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. Cuando cumples 27 años, mira el explicación más detallada o en el ejemplo a continuación.

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

330



UN cierre es muy parecido a un objeto. Se instancia cada vez que 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 cierre pertenece a, tiene acceso a cualquier variable que contenga.

Una variable está contenida en cierre si tu

  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 exterior cierre.

UN cierre sobrevive al tiempo de ejecución de la función que lo engendró. Si otras funciones salen de la cierre / alcanceen el que están definidos (por ejemplo, como valores de retorno), esos continuarán haciendo referencia a esa 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

319



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

Los cierres son una forma de dejar que una función   tener variables persistentes y privadas -   es decir, variables que solo una   función sabe, donde puede   realizar un seguimiento de la información de tiempos anteriores   que fue ejecutado

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

Publicación completa:

Entonces, ¿qué son estas cosas de cierre?


214



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 agregar 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_calculator crea una nueva variable local n, que sigue siendo utilizable por esa calculadora add y multiply funciones mucho después make_calculator devoluciones.

Si está familiarizado con los marcos de pila, estas calculadoras parecen extrañas: ¿Cómo pueden seguir accediendo? n después make_calculator ¿devoluciones? 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 de función que los hizo volver.

Funciones internas como add y multiply, qué variables de acceso declaradas en una función externa**, son llamados cierres.

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



* Por ejemplo, cubre todos los puntos en el artículo "Closures for Dummies" (Cerramientos 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 buen hecho que debe conocerse pero que no está relacionado con los cierres. También cubre todos los puntos en la respuesta aceptada, a excepción de 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 nuevamente no tienen relación 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 de esta respuesta o este comentario, que es que JavaScript hace que sea difícil de conectar el corriente valor de una variable de bucle en su función interna: El paso "conectar" solo se puede hacer 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 algo enchufado). De nuevo, es 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 cierres que funcionan de manera diferente en lenguajes funcionales como ML, donde las variables están ligadas a valores en lugar de espacio de almacenamiento, proporcionando un flujo constante de personas que entienden los cierres de una manera (es decir, el modo 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.


199