2010-06-01 9 views
10

Estoy leyendo "Javascript: The Good Parts" y estoy totalmente desconcertado por lo que realmente está pasando aquí. Una explicación más detallada y/o simplificada sería muy apreciada.Cierres: ¿Explicación de línea por línea del ejemplo "Javascript: buenas piezas"?

// BAD EXAMPLE 

// Make a function that assigns event handler functions to an array of nodes the wrong way. 
// When you click on a node, an alert box is supposed to display the ordinal of the node. 
// But it always displays the number of nodes instead. 

var add_the_handlers = function (nodes) { 
    var i; 
    for (i = 0; i < nodes.length; i += 1) { 
     nodes[i].onclick = function (e) { 
      alert(i); 
     } 
    } 
}; 

// END BAD EXAMPLE 

La función add_the_handlers se pretende dar a cada controlador de un número único (i). Se produce un error debido a que las funciones de controlador están ligados a la variable i, no el valor de la variable i en el momento en que se hizo la función:

// BETTER EXAMPLE 

// Make a function that assigns event handler functions to an array of nodes the right way. 
// When you click on a node, an alert box will display the ordinal of the node. 

var add_the_handlers = function (nodes) { 
    var i; 
    for (i = 0; i < nodes.length; i += 1) { 
     nodes[i].onclick = function (i) { 
      return function (e) { 
       alert(i); 
      }; 
     }(i); 
    } 
}; 

Ahora, en lugar de asignar una función a onclick, definimos una función y Invocarlo de inmediato, pasando en i. Esa función devolverá una función de controlador de eventos que está vinculada al valor de i que se transfirió, no al i definido en add_the_handlers. Esa función devuelta se asigna a onclick.

+0

Ver las preguntas etiquetadas en: http://stackoverflow.com/questions/tagged/javascript+closures+loops – CMS

+0

Usted también puede jugar con una demostración en vivo http://jsbin.com/sezisalulede/1/edit?html,js,output –

Respuesta

20

Creo que esta es una fuente muy común de confusión para los recién llegados a JavaScript. En primer lugar me permito sugerir la salida a la siguiente artículo Mozilla Dev por breve introducción sobre el tema de los cierres y ámbito léxico:

Vamos a empezar con el malo:

var add_the_handlers = function (nodes) { 
// Variable i is declared in the local scope of the add_the_handlers() 
// function. 
    var i; 

// Nothing special here. A normal for loop. 
    for (i = 0; i < nodes.length; i += 1) { 

// Now we are going to assign an anonymous function to the onclick property. 
     nodes[i].onclick = function (e) { 

// The problem here is that this anonymous function has become a closure. It 
// will be sharing the same local variable environment as the add_the_handlers() 
// function. Therefore when the callback is called, the i variable will contain 
// the last value it had when add_the_handlers() last returned. 
      alert(i); 
     } 
    } 

// The for loop ends, and i === nodes.length. The add_the_handlers() maintains 
// the value of i even after it returns. This is why when the callback 
// function is invoked, it will always alert the value of nodes.length. 
}; 

Podemos abordar este problema con más cierres, como sugirió Crockford en el "buen ejemplo". Un cierre es un tipo especial de objeto que combina dos cosas: una función y el entorno en el que se creó esa función. En JavaScript, el entorno del cierre consta de las variables locales que estaban dentro del alcance en el momento en que el cierre fue creado:

// Now we are creating an anonymous closure that creates its own local 
// environment. I renamed the parameter variable x to make it more clear. 
nodes[i].onclick = function (x) { 

    // Variable x will be initialized when this function is called. 

    // Return the event callback function. 
    return function (e) { 
     // We use the local variable from the closure environment, and not the 
     // one held in the scope of the outer function add_the_handlers(). 
     alert(x); 
    }; 
}(i); // We invoke the function immediately to initialize its internal 
     // environment that will be captured in the closure, and to receive 
     // the callback function which we need to assign to the onclick. 

En lugar de tener las devoluciones de llamada de todos los que comparten un mismo entorno, la función de cierre crea una nuevo entorno para cada uno. También podríamos haber utilizado una fábrica de función para crear un cierre, como en el siguiente ejemplo:

function makeOnClickCallback (x) { 
    return function (e) { 
     alert(x); 
    }; 
} 

for (i = 0; i < nodes.length; i += 1) { 
    nodes[i].onclick = makeOnClickCallback(i); 
} 
+0

Una pregunta un tanto relacionada. ¿Qué significa "e" en la función (e), y podría ser reemplazado con cualquier var? Solía ​​pensar que significaba un evento, pero ahora estoy confundido. – Matrym

+0

@Matrym: Sí, debería ser un argumento que el navegador pasa a la función de devolución de llamada cuando se produce el evento onclick. Consulte [este artículo de modo peculiar] (http://www.quirksmode.org/js/events_access.html) sobre cómo se maneja esto en diferentes navegadores. –

+0

¿Qué sucede si no usamos una variable? ¿Pasamos para que podamos encadenar cosas? – Matrym

3

Todo se trata de cierres. En el primer ejemplo, "i" será igual a "nodes.length" para cada controlador de eventos de clics, porque usa "i" del bucle que crea los controladores de eventos. Para cuando se llame al controlador de eventos, el ciclo habrá finalizado, por lo que "i" será igual a "nodes.length".

En el segundo ejemplo, "i" es un parámetro (por lo que una variable local). Los controladores de eventos usarán el valor de la variable local "i" (el parámetro).

0

Tiene que ver con el cierre.

Al hacer la cosa en el mal ejemplo,

al hacer clic en cada nodo, obtendrá el último valor i (es decir, usted tiene 3 nodos, no importa qué nodo se hace clic obtendrá 2). ya que su alerta (i) está vinculada a una referencia de la variable iy no al valor de i en el momento en que se vinculó en el controlador de eventos.

Haciéndolo de la mejor manera ejemplo, lo vinculó a lo que yo como en el momento en que fue repetido, por lo que al hacer clic en el nodo 1 obtendrá 0, el nodo 2 le dará 1 y el nodo 3 le dará 2 .

básicamente, estás evaluando lo que soy inmediatamente cuando se llama en la línea} (i) y pasó al parámetro e que ahora contiene el valor de lo que soy en ese momento.

Por cierto ... Creo que hay un error tipográfico en el mejor ejemplo ... debe estar alerta (e) en lugar de alerta (i).

2

En ambos ejemplos, cualquier nodo que haya pasado tiene un controlador de eventos onclick vinculado a él (al igual que <img src="..." onclick="myhandler()"/>, lo cual es una mala práctica después de todo).

La diferencia es que en el mal ejemplo cada cierre (el controlador de eventos funciona, es decir) hace referencia a la misma variable exacta i debido a su alcance principal común.

El buen ejemplo hace uso de una función anónima que se ejecuta de inmediato. Esta función anónima hace referencia a la misma variable exacta i que en el ejemplo incorrecto PERO, dado que se ejecuta y se proporciona con i como su primer parámetro, el valor de i se asigna a una variable local llamada ... ¿eh? ... i, exactamente - sobrescribiendo el definido en el alcance del padre.

Vamos a reescribir el buen ejemplo para que sea del todo claro:

var add_the_handlers = function (nodes) { 
    var i; 
    for (i = 0; i < nodes.length; i += 1) { 
     nodes[i].onclick = function (newvar) { 
      return function (e) { 
       alert(nevar); 
      }; 
     }(i); 
    } 
}; 

Aquí se sustituyen i en la función de controlador de eventos vuelto con newvar y todavía funciona, porque newvar es justo lo que se espera - una nueva variable heredada del alcance de la función anónima.

Buena suerte descubriéndolo.

Cuestiones relacionadas