tutorial script español ejemplos definicion curso caracteristicas javascript loops closures

español - javascript pdf



Cierres de Javascript: pregunta de alcance variable (5)

Estoy leyendo el sitio del desarrollador de Mozilla sobre cierres, y me di cuenta en su ejemplo de errores comunes, tenían este código:

<p id="help">Helpful notes will appear here</p> <p>E-mail: <input type="text" id="email" name="email"></p> <p>Name: <input type="text" id="name" name="name"></p> <p>Age: <input type="text" id="age" name="age"></p>

y

function showHelp(help) { document.getElementById(''help'').innerHTML = help; } function setupHelp() { var helpText = [ {''id'': ''email'', ''help'': ''Your e-mail address''}, {''id'': ''name'', ''help'': ''Your full name''}, {''id'': ''age'', ''help'': ''Your age (you must be over 16)''} ]; for (var i = 0; i < helpText.length; i++) { var item = helpText[i]; document.getElementById(item.id).onfocus = function() { showHelp(item.help); } } }

y dijeron que para el evento onFocus, el código solo mostraría ayuda para el último elemento porque todas las funciones anónimas asignadas al evento onFocus tienen un cierre alrededor de la variable ''item'', lo cual tiene sentido porque en JavaScript las variables no tienen alcance del bloque. La solución fue usar ''let item = ...'' en su lugar, para entonces tiene alcance de bloque.

Sin embargo, lo que me pregunto es por qué no podrías declarar ''var item'' justo encima del ciclo for? Luego tiene el alcance de setupHelp (), y cada iteración le está asignando un valor diferente, que luego sería capturado como su valor actual en el cierre ... ¿verdad?


Es porque en el momento en que se evalúa item.help , el ciclo se habría completado en su totalidad. En cambio, puedes hacer esto con un cierre:

for (var i = 0; i < helpText.length; i++) { document.getElementById(helpText[i].id).onfocus = function(item) { return function() {showHelp(item.help);}; }(helpText[i]); }

JavaScript no tiene ámbito de bloque, pero sí tiene ámbito de función. Al crear un cierre, estamos capturando la referencia a helpText[i] permanentemente.


Incluso si se declara fuera del bucle for, cada una de las funciones anónimas seguirá refiriéndose a la misma variable, por lo que después del bucle, todas seguirán apuntando al valor final del elemento.


Los nuevos ámbitos solo se crean en bloques de function (y with , pero no usan eso). Los bucles como for no crean nuevos ámbitos.

Entonces, incluso si declara la variable fuera del ciclo, se encontrará con el mismo problema.


Me doy cuenta de que la pregunta original tiene cinco años ... Pero también podría vincular un ámbito diferente / especial a la función de devolución de llamada que asigna a cada elemento:

// Function only exists once in memory function doOnFocus() { // ...but you make the assumption that it''ll be called with // the right "this" (context) var item = helpText[this.index]; showHelp(item.help); }; for (var i = 0; i < helpText.length; i++) { // Create the special context that the callback function // will be called with. This context will have an attr "i" // whose value is the current value of "i" in this loop in // each iteration var context = {index: i}; document.getElementById(helpText[i].id).onfocus = doOnFocus.bind(context); }

Si quieres un trazador de líneas (o cerca de él):

// Kind of messy... for (var i = 0; i < helpText.length; i++) { document.getElementById(helpText[i].id).onfocus = function(){ showHelp(helpText[this.index].help); }.bind({index: i}); }

O mejor aún, puede usar el array.prototype.forEach de EcmaScript 5.1, que soluciona el problema del alcance por usted.

helpText.forEach(function(help){ document.getElementById(help.id).onfocus = function(){ showHelp(help); }; });


Un cierre es una función y el entorno del ámbito de esa función.

Ayuda a comprender cómo Javascript implementa el alcance en este caso. De hecho, es solo una serie de diccionarios anidados. Considera este código:

var global1 = "foo"; function myFunc() { var x = 0; global1 = "bar"; } myFunc();

Cuando el programa comienza a ejecutarse, tiene un diccionario de alcance único, el diccionario global, que puede tener una serie de elementos definidos en él:

{ global1: "foo", myFunc:<function code> }

Digamos que llamas myFunc, que tiene una variable local x. Se crea un nuevo alcance para la ejecución de esta función. El alcance local de la función se ve así:

{ x: 0 }

También contiene una referencia a su alcance principal. Entonces todo el alcance de la función se ve así:

{ x: 0, parentScope: { global1: "foo", myFunc:<function code> } }

Esto permite que myFunc modifique global1. En Javascript, siempre que intenta asignar un valor a una variable, primero verifica el alcance local para el nombre de la variable. Si no se encuentra, comprueba el parentScope y el parentScope del alcance, etc. hasta que se encuentre la variable.

Un cierre es literalmente una función más un puntero al alcance de esa función (que contiene un puntero a su alcance principal, y así sucesivamente). Entonces, en su ejemplo, después de que el bucle for haya terminado de ejecutarse, el alcance podría verse así:

setupHelpScope = { helpText:<...>, i: 3, item: {''id'': ''age'', ''help'': ''Your age (you must be over 16)''}, parentScope: <...> }

Cada cierre que cree apuntará a este único objeto de alcance. Si tuviéramos que enumerar cada cierre que ha creado, se vería algo como esto:

[anonymousFunction1, setupHelpScope] [anonymousFunction2, setupHelpScope] [anonymousFunction3, setupHelpScope]

Cuando se ejecuta cualquiera de estas funciones, utiliza el objeto de ámbito que se aprobó; en este caso, ¡es el mismo objeto de alcance para cada función! Cada uno observará la misma variable de item y verá el mismo valor, que es el último establecido por su ciclo for .

Para responder a su pregunta, no importa si agrega var item encima del bucle for o dentro de él. Debido for bucles no crean su propio ámbito, el item se almacenará en el diccionario de alcance de la función actual, que es setupHelpScope . Los gabinetes generados dentro del bucle for siempre setupHelpScope a setupHelpScope .

Algunas notas importantes:

  • Este comportamiento se produce porque, en Javascript, los bucles no tienen su propio ámbito, solo usan el alcance de la función de cierre. Esto también es válido para if , while , switch , etc. Si fuera C #, por otro lado, se crearía un nuevo objeto de ámbito para cada ciclo, y cada cierre contendría un puntero a su propio alcance único.
  • Tenga en cuenta que si anonymousFunction1 modifica una variable en su ámbito, modifica esa variable para las otras funciones anónimas. Esto puede conducir a algunas interacciones realmente extrañas.
  • Los ámbitos son solo objetos, como los que usted programa. Específicamente, son diccionarios. La máquina virtual JS gestiona su eliminación de la memoria como cualquier otra cosa, con el recolector de basura. Por esta razón, el uso excesivo de cierres puede crear una hinchazón de memoria real. Dado que un cierre contiene un puntero a un objeto de ámbito (que a su vez contiene un puntero a su objeto de ámbito principal y así sucesivamente), no se puede recolectar la cadena completa del alcance y debe permanecer en la memoria.

Otras lecturas: