javascript español - ¿Cómo funcionan los cierres de JavaScript?




15 Answers

Cierres de JavaScript para principiantes

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

Los cierres no son mágicos

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

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

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

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

Dos breves resúmenes

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

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

Un ejemplo de un cierre.

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

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

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

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

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

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

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

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

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

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

Más ejemplos

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

Ejemplo 3

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

function say667() {
  // Local variable that ends up within closure
  var num = 42;
  var say = function() { console.log(num); }
  num++;
  return say;
}
var sayNumber = say667();
sayNumber(); // logs 43

Ejemplo 4

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

var gLogNumber, gIncreaseNumber, gSetNumber;
function setupSomeGlobals() {
  // Local variable that ends up within closure
  var num = 42;
  // Store some references to functions as global variables
  gLogNumber = function() { console.log(num); }
  gIncreaseNumber = function() { num++; }
  gSetNumber = function(x) { num = x; }
}

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

var oldLog = gLogNumber;

setupSomeGlobals();
gLogNumber(); // 42

oldLog() // 5

Las tres funciones tienen acceso compartido al mismo cierre: las variables locales de setupSomeGlobals() cuando se definieron las tres funciones.

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

Ejemplo 5

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

function sayAlice() {
    var say = function() { console.log(alice); }
    // Local variable that ends up within closure
    var alice = 'Hello Alice';
    return say;
}
sayAlice()();// logs "Hello Alice"

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

Ejemplo 6

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

Debe comprender la función de "elevación variable" en Javascript para comprender este ejemplo.

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

function testList() {
    var fnlist = buildList([1,2,3]);
    // Using j only to help prevent confusion -- could use i.
    for (var j = 0; j < fnlist.length; j++) {
        fnlist[j]();
    }
}

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

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

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

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

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

item0 undefined
item1 undefined
item2 undefined

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

item0 1
item1 2
item2 3

Ejemplo 7

En este ejemplo final, cada llamada a la función principal crea un cierre separado.

function newClosure(someNum, someRef) {
    // Local variables that end up within closure
    var num = someNum;
    var anArray = [1,2,3];
    var ref = someRef;
    return function(x) {
        num += x;
        anArray.push(num);
        console.log('num: ' + num +
            '; anArray: ' + anArray.toString() +
            '; ref.someVar: ' + ref.someVar + ';');
      }
}
obj = {someVar: 4};
fn1 = newClosure(4, obj);
fn2 = newClosure(5, obj);
fn1(1); // num: 5; anArray: 1,2,3,5; ref.someVar: 4;
fn2(1); // num: 6; anArray: 1,2,3,6; ref.someVar: 4;
obj.someVar++;
fn1(2); // num: 7; anArray: 1,2,3,5,7; ref.someVar: 5;
fn2(2); // num: 8; anArray: 1,2,3,6,8; ref.someVar: 5;

Resumen

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

Puntos finales:

  • Cuando se utiliza la function dentro de otra función, se utiliza un cierre.
  • Siempre que use eval() dentro de una función, se usa un cierre. El texto que eval puede hacer referencia a las variables locales de la función, y dentro de eval incluso puede crear nuevas variables locales utilizando eval('var foo = …')
  • Cuando utiliza la new Function(…) (el constructor de funciones ) dentro de una función, no crea un cierre. (La nueva función no puede hacer referencia a las variables locales de la función externa).
  • Un cierre en JavaScript es como mantener una copia de todas las variables locales, tal como eran cuando una función salía.
  • Probablemente es mejor pensar que un cierre siempre se crea solo una entrada a una función, y las variables locales se agregan a ese cierre.
  • Se mantiene un nuevo conjunto de variables locales cada vez que se llama a una función con un cierre (dado que la función contiene una declaración de función dentro de ella, y se devuelve una referencia a esa función interna o se mantiene una referencia externa de alguna manera) ).
  • Dos funciones pueden parecer que tienen el mismo texto de origen, pero tienen un comportamiento completamente diferente debido a su cierre "oculto". No creo que el código JavaScript pueda realmente averiguar si una referencia de función tiene un cierre o no.
  • Si está intentando realizar modificaciones dinámicas en el código fuente (por ejemplo: myFunction = Function(myFunction.toString().replace(/Hello/,'Hola')); ), no funcionará si myFunction es un cierre ( por supuesto, nunca se le ocurriría hacer una sustitución de cadena de código fuente en tiempo de ejecución, pero ...).
  • Es posible obtener declaraciones de funciones dentro de las declaraciones de funciones dentro de las funciones y mdash, y puede obtener cierres en más de un nivel.
  • Creo que normalmente un cierre es un término para la función junto con las variables que se capturan. Tenga en cuenta que no uso esa definición en este artículo!
  • Sospecho que los cierres en JavaScript difieren de los que se encuentran normalmente en los lenguajes funcionales.

Campo de golf

Gracias

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

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

que closure

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




PRÓLOGO: esta respuesta fue escrita 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". Bueno, intenté explicarle los cierres de JS a un amigo de 27 años y fracasé por completo.

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

Estoy bastante seguro de que fui una de las únicas personas que intentaron 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. Esperemos que la idea general de la historia siga siendo divertida para algunos.

Soy un gran fan de la analogía y la metáfora cuando explico conceptos difíciles, así que déjame probar mi historia 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 y adultos.

    return {

Y a menudo les contaba su última aventura increíble 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 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.




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 se adentra en el ámbito externo al agregar una variable, cuyo único propósito es realizar un seguimiento del conteo. 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 incluir 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>

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 se creó, indefinidamente. Para aplicar esto 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 conteo interna (debido al comportamiento de cierre explicado anteriormente), esto se traduce en un ámbito privado para el uso por parte de función ... no 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 devuelta ...

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

¿Consíguelo? Entonces, en nuestro ejemplo principal, la variable de conteo está contenida dentro del cierre y siempre está 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 las lecturas como para la asignación a sus variables de ámbito privado.

Hay que ir ahora estás encapsulando completamente este comportamiento.

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




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

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



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

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

Una variable está contenida en el cierre si

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

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

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

Ejemplo

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

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

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

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

Salida

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



Los cierres son simples:

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

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

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

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

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

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

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

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

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

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


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

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




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

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

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

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

outerFunction(1);​

* AC # pregunta




Wikipedia sobre cierres :

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

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

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

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

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

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

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

ems

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




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

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

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

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

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

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

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

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

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

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

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

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

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

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

Eso es todo lo que hay que contar.




Ejemplo para el primer punto por dlaliberte:

Un cierre no solo se crea cuando devuelves una función interna. De hecho, la función de encerramiento no necesita regresar en absoluto. 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 en la que podría usarse de inmediato. Por lo tanto, el cierre de la función de encierro probablemente ya existe en el momento en que se llamó a la función de encierro, ya que cualquier función interna tiene acceso a ella tan pronto como se llama.

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



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

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

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



Las funciones de JavaScript pueden acceder a su:

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

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

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

Ejemplo de un cierre que utiliza el entorno global:

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

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

var isVotedUp = false;
var isVotedDown = false;

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

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

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

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

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




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

function sing(person) {

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

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

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

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

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

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

var person="an old lady";

sing(person);

INSTRUCCIONES

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

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

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

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

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

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

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

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

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

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

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

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

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

El código largo ejemplo anterior

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

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

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

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

var person="an old lady";

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

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

Después de esto tenemos la línea

sing(person);

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

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

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

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

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

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

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

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

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




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

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

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

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

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

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




Tal vez un poco más allá de todos, pero 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 fuera un clic para mí.

Un cierre es una función que tiene acceso al alcance de otra función (sus variables y funciones). La forma más fácil 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 outerFunction que, a su vez, llama a innerFunction. Tenga en cuenta cómo es una función de la barra externa disponible para la función interna, como lo demuestra su alerta correcta sobre el valor de la barra externa.

Ahora considera lo siguiente:

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

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

ALERTA: mono

referenceToInnerFunction se establece en outerFunction (), que simplemente devuelve una referencia a innerFunction. Cuando se llama a referenceToInnerFunction, devuelve outerVar. Nuevamente, como se indicó anteriormente, esto demuestra que innerFunction tiene acceso a outerVar, una variable de outerFunction. Además, es interesante observar que conserva este acceso incluso después de que outerFunction haya terminado de ejecutarse.

Y aquí es donde las cosas se ponen realmente interesantes. Si tuviéramos que deshacernos de outerFunction, supongamos que sea 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? ¿Cómo puede referenceToInnerFunction saber el valor de outerVar ahora que outerFunction se ha establecido en nulo?

La razón por la que referenceToInnerFunction aún puede acceder al valor de outerVar se debe a que cuando el cierre se creó por primera vez colocando innerFunction dentro de outerFunction, innerFunction agregó una referencia al alcance de outerFunction (sus variables y funciones) a su cadena de alcance. Lo que esto significa es que innerFunction tiene un puntero o una referencia a todas las variables de outerFunction, incluida outerVar. Por lo tanto, incluso cuando outerFunction ha terminado de ejecutarse, o incluso si se eliminó o se estableció en nulo, las variables en su ámbito, como la externalVar, se quedan en la memoria debido a la referencia destacada a ellas en la parte de innerFunction que se ha devuelto referenceToInnerFunction. Para liberar verdaderamente la memoria externa y el resto de las variables de outerFunction de la memoria, tendría que deshacerse de esta referencia destacada.Por ejemplo, al establecer 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

Segundo, cuando se crea un cierre, conserva una referencia a todas las variables y funciones de su función de cierre; no llega a escoger y elegir. Y, pero así, los cierres se deben usar con moderación, o al menos con cuidado, ya que pueden requerir mucha memoria; muchas variables pueden mantenerse en la memoria mucho tiempo después de que una función contenedora haya terminado de ejecutarse.




Related

javascript function variables scope closures