¿Qué problemas o soluciones demuestran realmente la utilidad práctica de los cierres?

Aquí hay un ejemplo simple, donde dos botones se agregan dinámicamente a un elemento DIV:

Buttons:
 function addButton (name, message, parentId) { var b = document.createElement("INPUT"); b.type = "button"; b.value = name; b.onclick = function() { alert (message); } document.getElementById(parentId).appendChild (b); } addButton("button 1", "hello world", 'container'); addButton("button 2", "goodbye world", 'container'); 

El código anterior creará dos botones, y cuando haga clic en ellos, cada uno mostrará un mensaje diferente (“hola mundo” y “adiós mundo”). El mensaje variable (pasado como argumento para addButton () ), es visible dentro de la función anónima establecida como el clic de cada botón.

Cuando se llama a la función onclick (debido a que un usuario hace clic en el botón, lo que podría pasar mucho tiempo después), esa función puede ver el valor del mensaje variable, aunque la función addButton () que contiene esa variable, regresó hace mucho tiempo .

De esta manera, los cierres permiten que las funciones se empaqueten junto con los datos . Aunque parece que los dos botones obtienen una función idéntica para su clic , las funciones no son realmente idénticas porque esas funciones “cierran” valores diferentes para el mensaje .

Hay otras formas en que un lenguaje podría lograr este tipo de cosas, pero la forma de usar los cierres de Javascript puede ser bastante útil.

Sin embargo, puede haber algunas desventajas confusas en el uso de cierres. Digamos que haces algo como esto:

Peligro: no es lo que esperas que pase aquí

 function addButtons (howMany, parentId) { for (var i=1; i<=howMany; i++) { var b = document.createElement("INPUT"); b.type = "button"; b.value = "button " + i; b.onclick = function() { alert ("button " + i + " was clicked"); } document.getElementById(parentId).appendChild (b); } } addButtons(5, 'container'); 

Al ejecutar esto, se crean 5 botones, denominados “botón 1” a “botón 5”. Cuando hace clic en el primero … en lugar de decir “se hizo clic en el botón 1”, en su lugar dirá “se hizo clic en el botón 6”. Como todos ellos.

La razón es que en el momento en que se hace clic en el botón, la variable ‘i’ en realidad es 6. Una solución a esto, que se ve bastante fea, es crear e inmediatamente llamar a una función anónima de ajuste que devuelve su función onclick, así:

Esto funciona mucho mejor, pero es un poco confuso:

 function addButtons (howMany, parentId) { for (var i=1; i<=howMany; i++) { var b = document.createElement("INPUT"); b.type = "button"; b.value = "button " + i; b.onclick = (function(which){ return function() { alert ("button " + which + " was clicked"); } })(i); document.getElementById(parentId).appendChild (b); } } addButtons(5, 'container'); 

Ahora, cada vez a través del ciclo, creará un nuevo contexto, cada uno de los cuales tiene una variable “cuál”, y cada uno tendrá valores diferentes (1-5). Entonces obtienes los resultados que esperas.

Solo para aclarar, los Closures son una característica del lenguaje Javascript, no una técnica de programación exactamente. Esta característica permite que las funciones anidadas obtengan los valores creados en el contexto de ejecución de la función primaria.

Un cierre es algo que sucede cuando tiene funciones anidadas. ¡Es posible que estés usando Closures sin siquiera saberlo!

Sin embargo, aquí hay algunos ejemplos claros de cómo podría usar funciones anidadas:

Patrón del módulo

  var myModule = (function () {
 
 var fname = "Phil";
 var lname = "Dunphy";
 var hobby = "Rompiendo chistes y escribiendo Phils-osophy";
 
 var work = function () {
         console.log (fname + "" + lname + "es un agente inmobiliario y su pasatiempo es" + hobby);
 } 
 
 regreso { 
      "hobby": hobby
       "trabajo Trabajo
 }
 }) ();
 
 myModule.fname;  // indefinido
 myModule.lname;  // indefinido
 myModule.hobby;  // Rompiendo chistes y escribiendo Phils-osophy
 myModule.work ();  // Phil Dunphy es un agente de bienes raíces y su pasatiempo es Cracking 
  • fname y lname son privados para myModule y no están expuestos.
  • Puede anular myModule.work o myModule.hobby
  • Se crea un cierre alrededor de myModule. Ahora, todo lo que devuelve la función myModule está expuesto al resto del código. Los objetos devueltos conservan el contexto en el que se creó.
  • El contexto significa todas las variables y objetos (incluidas las funciones) que estaban disponibles cuando se ejecutaba la expresión de la función.
  • La referencia a fname y lname se mantiene en la memoria siempre que haya alguna referencia a ellos a través de myModule.work en este caso.

Alcance léxico

  var myFun = function () {
         var a = 5;        
         var yourFun = function () {
                var b = 10;
                console.log ('En la función interna: a =' + a + 'y b =' + b);
         }
         return {"a": a, "b": b, "fun": yourFun}
 }
 console.log ("a =" + myFun.a + "b =" + myFun.b);
  myFun.yourFun ();

Este es un ejemplo más básico. Aquí, funciones como yourFun tienen acceso a variables de las funciones externas.

El alcance de la variable “a” es myFun. El alcance de la variable “b” es yourFun. yourFun tiene acceso a la variable a a través de un cierre.

Notación literal de objeto

  var myObj = {     
      "fname": "Phil",     
       "lname": "Dunphy",    
       "función del trabajo() {            
         console.log (this.fname + "" + this.lname + "es un agente de bienes raíces");          
  } 
 } 

 console.log (myObj.fname); console.log (myObj.lname); myObj.work ();
  • Ahora, ¿qué pasa si no quiero exponer fname y lname en myObj? ¿Cómo mantener algunas partes de mi código privadas para ese objeto y exponer parte del código selectivamente? JavaScript no tiene ningún concepto de modificadores de acceso como público, privado, paquete y protegido. ¿Asi que que hacemos? Para resolver este problema, veamos qué patrón de Módulo hay en la siguiente sección de respuesta.

Espero que esto ayude. Puede leer más sobre los cierres en profundidad aquí:

Comprender los cierres de Javascript

La página de MDN sobre el tema tiene algunos buenos ejemplos de aplicaciones prácticas: https://developer.mozilla.org/en

Los cierres son una de las características que, en mi opinión, hacen que JavaScript sea tan hermoso (el lenguaje también tiene muchos defectos, famoso; consulte http://oreilly.com/javascript/ex … y http://wtfjs.com )

He encontrado que los cierres son particularmente útiles para permitir la encapsulación de comportamientos complejos, particularmente lo que requiere mantener el estado, en una sola función. El uso de cierres permite que esto se logre sin ningún impacto en contener ámbitos más allá de la definición de la función (lo cual me parece particularmente elegante).

Por ejemplo, la función subdued continuación le permite “someter” el comportamiento de algunos de los eventos DOM más excitables, como desplazarse y cambiar el tamaño. Con desplazamiento, por ejemplo, se llamará a su oyente de eventos hasta una vez por cada píxel desplazado. A veces solo quieres saber cuándo comienza y termina el desplazamiento.

subdued toma (opcionalmente) dos funciones como argumentos. El primero ( cbStart ) se llama cuando se inicia una secuencia de eventos y el segundo ( cbEnd ) cuando se detiene la secuencia. El tercer argumento es el tiempo en milisegundos utilizado para determinar el final de la secuencia. subdued devuelve una función que se puede usar como detector de eventos y pasa el contexto de evento relevante a las devoluciones de llamada.

En este ejemplo, las variables timeout , eventArgs , eventThis y los tres parámetros de subdued se almacenan en el cierre:

 function subdued (cbStart, cbEnd, minInterval) { var timeout; minInterval = minInterval || 200; // default 200ms return function () { var eventArgs = Array.prototype.slice.call(arguments), eventThis = this; // timeout not set? call the start callback !timeout && cbStart && cbStart.apply(eventThis, eventArgs); // another event within timeout period; cancel the scheduled `cbEnd` call clearTimeout(timeout); timeout = setTimeout(function () { // If you've got here, then it's been `minInterval` milliseconds // since the last event timeout = undefined; cbEnd && cbEnd.apply(eventThis, eventArgs); }, minInterval); }; }; function scrollStart () { console.log("scrolling started"); }; function scrollEnd () { console.log("scrolling ended"); }; document.addEventListener('scroll', subdued(scrollStart, scrollEnd), true); 

Editar: eliminó el ejemplo de emulación de campo / método privado y lo vinculó a una página MDN relevante (que también incluye un ejemplo más claro de esta aplicación)

Una forma en que he visto que los cierres funcionan bien es cuando tienes toneladas de LOC de múltiples desarrolladores distribuidos en una aplicación muy grande. Ser capaz de nombrar espacios de nombres a través de cierres en lugar de en el nombre de la variable es conveniente y limpio, funciona muy bien en un entorno en el que modulariza JS en múltiples archivos (con suerte utilizando algún paquete para reducir las solicitudes HTTP) para diferentes páginas / secciones. Por ejemplo, esto:

 var articleNavMenuLinks = $('#nav').find('a'); function articleNavMenuDoSomething(){ articleNavMenuLinks.click(function(){ ... do something ... }); }; 

se convierte en esto:

 (function(){ var links = $('#nav').find('a'); function doSomething(){ links.click(function(){ ... do something ... }); }; })(); 

porque no importa si usa “enlaces” en otro lugar como nombre de variable, o “doSomething” como nombre de función, porque ahora todo está fuera del espacio de nombres global.