meaning - Cierres: ¿Explicación de línea por línea del ejemplo "Javascript: Good Parts"?
javascript hoisting (4)
Creo que esta es una fuente muy común de confusión para los recién llegados a JavaScript. Primero sugeriría revisar el siguiente artículo de Mozilla Dev para una breve introducción sobre el tema de los cierres y el alcance léxico:
Comencemos 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 consiste en cualquier variable local que estuviera en el ámbito en el momento en que se creó el cierre:
// 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 todas las devoluciones de llamadas que comparten un único entorno, la función de cierre crea un nuevo entorno para cada una. También podríamos haber usado una fábrica de funciones 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);
}
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.
// 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
estaba destinada a dar a cada controlador un número único (i). No funciona porque las funciones del controlador están vinculadas a la variable i
, no al valor de la variable i
en el momento en que se realizó 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 asignarle una función a onclick, definimos una función e inmediatamente la invocamos, pasando en i
. Esa función devolverá una función de controlador de eventos que está vinculada al valor de i
que se add_the_handlers
, no al que se definió en add_the_handlers
. Esa función devuelta se asigna a onclick.
En ambos ejemplos, cualquier nodo que haya pasado tiene un controlador de eventos onclick vinculado (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 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 que en el ejemplo malo PERO, dado que se ejecuta y se le proporciona 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 dejarlo 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í reemplazamos i
en la función del controlador de eventos devuelto con newvar
y todavía funciona, porque newvar
es justo lo que esperaría, una nueva variable heredada del alcance de la función anónima.
Buena suerte descubriéndolo.
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 tanto, una variable local). Los controladores de eventos usarán el valor de la variable local "i" (el parámetro).
Tiene que ver con el cierre.
Cuando haces la cosa en el mal ejemplo,
cuando haces clic en cada nodo, obtendrás el último valor i (es decir, tienes 3 nodos, sin importar en qué nodo hagas clic obtendrás 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 posible, lo vinculó a lo que en ese momento 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 la mejor parte del ejemplo ... debe estar alerta (e) en lugar de alerta (i).